Professional Refactoring in Visual Basic

555

Transcript of Professional Refactoring in Visual Basic

Page 1: Professional Refactoring in Visual Basic
Page 2: Professional Refactoring in Visual Basic

Professional

Refactoring in Visual Basic®

Danijel Arsenovski

79796ffirs.qxd:WroxPro 2/22/08 4:57 PM Page iii

Page 3: Professional Refactoring in Visual Basic

To Paola

79796ffirs.qxd:WroxPro 2/22/08 4:57 PM Page v

Page 4: Professional Refactoring in Visual Basic

Professional

Refactoring in Visual Basic®

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxi

Part I: Introduction to Refactoring

Chapter 1: Refactoring: What’s All the Fuss About? . . . . . . . . . . . . . . . . . . . . 3Chapter 2: A First Taste of Refactoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19Chapter 3: Assembling a Refactoring Toolkit . . . . . . . . . . . . . . . . . . . . . . . . 59Chapter 4: Rent-a-Wheels Application Prototype . . . . . . . . . . . . . . . . . . . . . . 87

Part II: Preliminary VB Refactorings

Chapter 5: Chameleon Language: From Weak Static Typing to Strong Dynamic Typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109

Chapter 6: Error Handling: From Legacy to Structured in a Few Easy Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147

Chapter 7: Basic Hygiene: Eliminating Dead Code, Reducing Scope, Using Explicit Imports, and Removing Unused References . . . . . 173

Part III: Getting Started with Standard RefactoringTransformations

Chapter 8: From Problem Domain to Code: Closing the Gap. . . . . . . . . . . . . 197Chapter 9: The Method Extraction Remedy for Duplicated Code . . . . . . . . . 219Chapter 10: Method Consolidation and Extraction Techniques . . . . . . . . . . 243

Part IV: Advanced Refactorings

Chapter 11: Discovering Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271Chapter 12: Advanced Object-Oriented Concepts and

Related Refactorings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323Chapter 13: Code Organization on a Large Scale. . . . . . . . . . . . . . . . . . . . . 371

Continues

79796ffirs.qxd:WroxPro 2/22/08 4:57 PM Page i

Page 5: Professional Refactoring in Visual Basic

Part V: Refactoring Applied

Chapter 14: Refactoring to Patterns. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407Chapter 15: LINQ and Other VB 2008 Enhancements . . . . . . . . . . . . . . . . . 439Chapter 16: The Future of Legacy VB Code. . . . . . . . . . . . . . . . . . . . . . . . . 465Appendix A: Unleash Refactor!. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 483Appendix B: Rent-a-Wheels Prototype Internals and Intricacies . . . . . . . . . 487Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507

79796ffirs.qxd:WroxPro 2/22/08 4:57 PM Page ii

Page 6: Professional Refactoring in Visual Basic

Professional

Refactoring in Visual Basic®

Danijel Arsenovski

79796ffirs.qxd:WroxPro 2/22/08 4:57 PM Page iii

Page 7: Professional Refactoring in Visual Basic

Professional Refactoring in Visual Basic®Published byWiley Publishing, Inc.10475 Crosspoint BoulevardIndianapolis, IN 46256www.wiley.com

Copyright © 2008 by Wiley Publishing, Inc., Indianapolis, IndianaPublished simultaneously in CanadaISBN: 978-0-470-17979-6Manufactured in the United States of America10 9 8 7 6 5 4 3 2 1

Library of Congress Cataloging-in-Publication DataArsenovski, Danijel, 1973–Professional refactoring in Visual Basic / Danijel Arsenovski.

p. cm.Includes bibliographical references and index.ISBN 978-0-470-17979-6 (paper/website)1. Microsoft Visual BASIC. 2. Computer software—Development 3. Source code (Computer science) I. Title.QA76.76.D47A772 2008005.2'762—dc22

2008001376No part of this publication may be reproduced, stored in a retrieval system or transmitted in any form orby any means, electronic, mechanical, photocopying, recording, scanning or otherwise, except as permittedunder Sections 107 or 108 of the 1976 United States Copyright Act, without either the prior written permis-sion of the Publisher, or authorization through payment of the appropriate per-copy fee to the CopyrightClearance Center, 222 Rosewood Drive, Danvers, MA 01923, (978) 750-8400, fax (978) 646-8600. Requests to the Publisher for permission should be addressed to the Legal Department, Wiley Publishing, Inc., 10475 Crosspoint Blvd., Indianapolis, IN 46256, (317) 572-3447, fax (317) 572-4355, or online athttp://www.wiley.com/go/permissions.

Limit of Liability/Disclaimer of Warranty: The publisher and the author make no representations or war-ranties with respect to the accuracy or completeness of the contents of this work and specifically disclaim allwarranties, including without limitation warranties of fitness for a particular purpose. No warranty may becreated or extended by sales or promotional materials. The advice and strategies contained herein may not besuitable for every situation. This work is sold with the understanding that the publisher is not engaged in ren-dering legal, accounting, or other professional services. If professional assistance is required, the services of acompetent professional person should be sought. Neither the publisher nor the author shall be liable for dam-ages arising herefrom. The fact that an organization or Website is referred to in this work as a citation and/ora potential source of further information does not mean that the author or the publisher endorses the infor-mation the organization or Website may provide or recommendations it may make. Further, readers shouldbe aware that Internet Websites listed in this work may have changed or disappeared between when thiswork was written and when it is read.

For general information on our other products and services please contact our Customer Care Departmentwithin the United States at (800) 762-2974, outside the United States at (317) 572-3993 or fax (317) 572-4002.

Trademarks: Wiley, the Wiley logo, Wrox, the Wrox logo, Wrox Programmer to Programmer, and relatedtrade dress are trademarks or registered trademarks of John Wiley & Sons, Inc. and/or its affiliates, in theUnited States and other countries, and may not be used without written permission. Visual Basic is a regis-tered trademark of Microsoft Corporation in the United States and/or other countries. All other trademarksare the property of their respective owners. Wiley Publishing, Inc., is not associated with any product orvendor mentioned in this book.

Wiley also publishes its books in a variety of electronic formats. Some content that appears in print may notbe available in electronic books.

79796ffirs.qxd:WroxPro 2/22/08 4:57 PM Page iv

Page 8: Professional Refactoring in Visual Basic

To Paola

79796ffirs.qxd:WroxPro 2/22/08 4:57 PM Page v

Page 9: Professional Refactoring in Visual Basic

About the AuthorDanijel Arsenovski is a software developer from Santiago, Chile. Currently, he works as Product andSolutions Architect at Excelsys S.A, designing Internet banking solutions for numerous clients in the region.He started experimenting with refactoring while overhauling a huge banking system, and he hasn’t lostinterest in refactoring ever since. He pioneered the use of refactoring as a vehicle for a VB 6 code upgrade toVB .NET. Arsenovski is a contributing author for Visual Studio Magazine and Visual Systems Journal, holds aMicrosoft Certified Solution Developer (MCSD) certification, and was named Visual Basic MVP in 2005. You can reach him at [email protected], and you can take a look at his blog athttp://blog.vbrefactoring.com.

79796ffirs.qxd:WroxPro 2/22/08 4:57 PM Page vi

Page 10: Professional Refactoring in Visual Basic

Acquisitions EditorKatie Mohr

Senior Development EditorKevin Kent

Technical EditorDoug Holland

Copy EditorsS. B. KleinmanMildred Sanchez

Editorial ManagerMary Beth Wakefield

Production ManagerTim Tate

Vice President and Executive Group PublisherRichard Swadley

Vice President and Executive PublisherJoseph B. Wikert

Project Coordinator, CoverLynsey Stanford

CompositorCraig James Woods, Happenstance Type-O-Rama

ProofreaderWord One

IndexerJohnna VanHoose Dinse

Credits

79796ffirs.qxd:WroxPro 2/22/08 4:57 PM Page vii

Page 11: Professional Refactoring in Visual Basic

Acknowledgments

A number of people deserve credit for helping me out in getting this book to the finish line. I am quite awarethat dealing with a first-time writer is always a challenge and probably means much more work than usual. Solet me start with my Wrox team. I want to thank Kevin Kent, my development editor, for always being there forme and for his meticulous work. He was always there with exactly the right dose of guidance. I want to thankKatie Mohr for believing in this project right from the outset. Thanks to Doug Holland, my technical editor, forproviding that all-important safety net that every technical author needs. Also thanks to Chris Webb for puttingme in contact with the right people at the right moment.

I want to especially thank Mark Miller from Developer Express for reviewing a great portion of themanuscript and giving invaluable feedback, particularly on sections related to the Refactor! for VBadd-in. Also thanks to the rest of the Developer Express team for giving me full access to their prod-ucts and for responding to my queries.

I want to thank Dusan Miloradovic for his review and excellent feedback. Other people that provided feed-back and made me believe I was on the right track are Dan Mabbutt, Sandeep Joshi, Anthony Williams, Rod Stephens, and others.

Thanks to Vukan Djurovic for convincing me that there is always a better way to write some code and toDiego Dagum for his interest in my work. Finally, thanks to Milos Milosavljevic for introducing me to theworld of Visual Basic.

79796ffirs.qxd:WroxPro 2/22/08 4:57 PM Page viii

Page 12: Professional Refactoring in Visual Basic

Contents

Acknowledgments viiiForeword xixIntroduction xxi

Part I: Introduction to Refactoring 1

Chapter 1: Refactoring: What’s All the Fuss About? 3

A Quick Refactoring Overview 4The Refactoring Process 4A Look at the Software Situation 5

The Refactoring Process: A Closer Look 7Using Code Smells 7Transforming the Code 8The Benefits of Refactoring 9Debunking Common Misconceptions 12

Visual Basic and Refactoring 14Visual Basic History and Legacy Issues 14Visual Basic Evolution 14Dealing with Legacy Issues Through Refactoring 16

Summary 16

Chapter 2: A First Taste of Refactoring 19

Calories Calculator Sample Application 19Calories Calculator Application 20Growing Requirements: Calculating Ideal Weight 22Growing Requirements: Persisting Patient Data 24

Refactoring in Action 26Decomposing the BtnCalculate_Click Method 27Discovering New Classes 29Narrowing the Patient Class Interface 32Putting Conditional Logic in the Patient Class 34Creating the Patient Class Hierarchy 37

Implementing the Persistence Functionality 43Saving the Data 44Implementing Patient-History Display Functionality 52

Calories Calculator, Refactored Version 56Summary 58

79796ftoc.qxd:WroxPro 2/23/08 8:45 AM Page ix

Page 13: Professional Refactoring in Visual Basic

x

Contents

Chapter 3: Assembling a Refactoring Toolkit 59

Using an Automated Refactoring Tool 60ReSharper from JetBrains 60Visual Assist X from Whole Tomato 61Refactor! Pro from Developer Express 61Getting Started with Refactor! 61Exploring Refactor! for the VB User Interface 63Quick Tour: Available Refactorings 67

Unit-Testing Basics: The Testing Harness 69Why a Unit-Testing Framework? 70Your First Taste of NUnit 72Installing NUnit 72Implementing Your First Test 74The Test-Driven Approach 82Other Test Tools to Consider 83

A Few Words about Version Control 84Summary 85

Chapter 4: Rent-a-Wheels Application Prototype 87

Interviewing the Client 88Interviewing the Manager 88Interviewing the Desk Receptionist 89Interviewing the Parking Lot Attendant 90Interviewing Maintenance Personnel 90

Taking the Initial Steps in the Rent-a-Wheels Project 91Actors and Use Cases 91Vehicle States 93First Sketch of the Main Application Window 94Rent-a-Wheels Team Meeting 96

Making the Prototype Work 96Examining the Database Model 96Examining the Visual Basic Code 99

Fast and Furious, a VB Approach to Programming 102Database-Driven Design 102GUI-Based Application 103Event-Driven Programming 103Rapid Application Development (RAD) 104Copy-Paste as a Code Reuse Mechanism 104

From Prototype to Delivery Through the Refactoring Process 105Summary 105

79796ftoc.qxd:WroxPro 2/23/08 8:45 AM Page x

Page 14: Professional Refactoring in Visual Basic

xi

Contents

Part II: Preliminary VB Refactorings 107

Chapter 5: Chameleon Language: From Weak Static Typing to Strong Dynamic Typing 109

Option Explicit and Option Strict, the .NET Effect 110Setting Option Explicit On in Relaxed Code 111

Understanding the Set Option Explicit On Refactoring 112Refactoring the Rent-a-Wheels Code to Explicit Form 114

Setting Option Strict On in Relaxed Code 115A Slightly Artificial Example of Permissive VB code 116Convoluted Use of Variables Resolved by the Definition of New Variables 119Inferring Variable Type 122Putting It All Together with Type-Conversion Functions 125Dealing with Methods, Fields, Properties, and Other Members 127Applying Set Option Strict On Refactoring to the Rent-a-Wheels Application 132

Static Versus Dynamic Typing and Visual Basic 135Late Binding in Visual Basic 6 and Prior 136Duck Typing 136Resetting Dynamic or Static Behavior at the File Level 138Providing a Statically Typed Wrapper for Dynamic Code 138

Activating Explicit and Strict Compiler Options 141Setting Options in the Project Properties 141Changing the Default Behavior of the Visual Basic Compiler 142Setting Options in Source Files 143Using Item Templates to Set Options 143

Summary 145

Chapter 6: Error Handling: From Legacy to Structured in a Few Easy Steps 147

Legacy Error Handling Versus Structured Error Handling 148Legacy (Unstructured) Error Handling 148Structured Error Handling 150

The Benefits of Structured Error Handling 153Structured Versus Unstructured Code 153Exceptions as Types, Not Numbers 154Error Filtering 154The Finally Block 155.NET Interoperability 155

Replacing the On Error Construct with Try-Catch-Finally 156Understanding the When Keyword 158Refactoring Steps for Replacing On Error with Try-Catch-Finally 158Replacing the On Error Goto Label with the Try-Catch-Finally Construct 159Replacing On Error Resume Next with the Try-Catch-Finally Construct 162

79796ftoc.qxd:WroxPro 2/23/08 8:45 AM Page xi

Page 15: Professional Refactoring in Visual Basic

xii

Contents

Replacing Error Code with Exception Type 164Replacing System Error Codes with Exception Types 166Replacing Custom Error Codes with Exception Types 168

Error Handling in the Rent-a-Wheels Application 169Application-Level Events in VB 2009 169

Summary 171

Chapter 7: Basic Hygiene: Eliminating Dead Code, Reducing Scope, Using Explicit Imports, and Removing Unused References 173

Eliminating Dead Code 174Types of Dead Code 175Common Sources of Dead Code 176

Reducing the Scope and Access Level of Unduly Exposed Elements 179Scope and Access Level 181Common Sources of Overexposure 182Dealing with Overexposure 186

Using Explicit Imports 187Imports Section Depicts Dependencies in Your System 188

Removing Unused Assembly References 191Basic Hygiene in the Rent-a-Wheels Application 192Summary 193

Part III: Getting Started with Standard RefactoringTransformations 195

Chapter 8: From Problem Domain to Code: Closing the Gap 197

Understanding the Problem Domain 198Step One: Gathering the Information 198Step Two: Agreeing on the Vocabulary 199Step Three: Describing the Interactions 200Step Four: Building the Prototype 201

Naming Guidelines 201Capitalization Styles 202Simple Naming Guidelines 203Good Communication: Choosing the Right Words 204Rename Refactoring 206

Published and Public Interfaces 208Self-Contained Applications Versus Reusable Modules 209Modifying the Public Interfaces 212Safe Rename Refactoring in Refactor! 214

Rename and Safe Rename Refactoring in the Rent-a-Wheels Application 217Summary 218

79796ftoc.qxd:WroxPro 2/23/08 8:45 AM Page xii

Page 16: Professional Refactoring in Visual Basic

xiii

Contents

Chapter 9: The Method Extraction Remedy for Duplicated Code 219

Why Keep Code Encapsulated and Details Hidden? 219Information and Implementation Hiding 220Decomposing Methods 223

Circumference Calculation — Long Method Example 223Extracting Circumference Length Calculation Code 226Extracting the Radius Calculation Code 229Extracting the “Wait for User to Close” Code 229Extracting the Read Coordinates Code 229Extract Method Refactoring in Refactor! 233

The Duplicated Code Smell 234Sources of Duplicated Code 235Copy-Paste Programming 236

Magic Literals 237Introduce Constant Refactoring in Refactor! 239

Extract Method and Replace Magic Literal Refactoring in the Rent-a-Wheels Application 240Summary 240

Chapter 10: Method Consolidation and Extraction Techniques 243

Dealing with Temporary Variables 243Move Declaration Near Reference Refactoring 244Move Initialization to Declaration Refactoring 248Split Temporary Variable Refactoring 249Inline Temp Refactoring 253Replace Temp with Query Refactoring 256

Method Reorganization Heuristics 258Method Reorganization and Rent-a-Wheels 259

Removing Duplication in Rent-a-Wheels 261Magic Literals, Comments, and Event-Handling Blindness in Rent-a-Wheels 263

Summary 267

Part IV: Advanced Refactorings 269

Chapter 11: Discovering Objects 271

A Quick Object-Oriented Programming Overview 272What Are Objects Anyway? 272Encapsulation and Objects 272Encapsulate Field Refactoring in Refactor! 274Object State Retention 276

79796ftoc.qxd:WroxPro 2/23/08 8:45 AM Page xiii

Page 17: Professional Refactoring in Visual Basic

xiv

Contents

Classes 276Object Identity 277Objects as Basic Building Blocks 278Root Object 278Object Lifetime and Garbage Collection 279Messages 280

Designing Classes 281Classes Are Nouns, Operations Are Verbs 284Classes, Responsibilities, and Collaborators 288Entities and Relationships 297

Discovering Hidden Classes 298Dealing with Database-Driven Design 299Moving From Procedural to Object-Oriented Design 302Keeping Domain, Presentation, and Persistence Apart 308Discovering Objects and the Rent-a-Wheels Application 313

Summary 320

Chapter 12: Advanced Object-Oriented Concepts and Related Refactorings 323

Inheritance, Polymorphism, and Genericity 324Inheritance 324Polymorphism 329Genericity 332

Inheritance Abuse and Refactoring Solutions 334Composition Mistaken for Inheritance and Other Misuses 337Refactoring for Inheritance — Print-System Illustration 342

Making Use of Generics 360Inheritance and Generic Types in the Rent-a-Wheels Application 364

Extracting Super 364Employing Generics 364Extract Data Objects Provider Class 365

Summary 369

Chapter 13: Code Organization on a Large Scale 371

Namespaces 371Naming Guidelines and Namespace Organization 372Nested Namespaces 372Changing the Root Namespace Name 372Using Import Statements 373

79796ftoc.qxd:WroxPro 2/23/08 8:45 AM Page xiv

Page 18: Professional Refactoring in Visual Basic

xv

Contents

Assemblies 375Binary Reuse 375Namespace Organization Guidelines 377Dependency Considerations 381

Visual Basic Project File Structure Organization 387Move Type to File Refactoring in Refactor! 388Partial Classes 390Inherited Form 390Abstract Form Inheritance 392Delegating Abstract Form Work to a Form Helper Class 392

Namespace Organization and Windows Forms Inheritance in Rent-a-Wheels 394Extracting Parent Administration Form through Abstract Form Helper Pattern Application 394Namespace and Assembly Reorganization 402

Summary 403

Part V: Refactoring Applied 405

Chapter 14: Refactoring to Patterns 407

Design Patterns: What’s All the Fuss About? 408Defining Design Patterns 408Classifying Patterns 409Pattern Elements 409Weighing the Benefits of Design Patterns 410Using Patterns 410

Example Design Pattern: Abstract Factory 411Name 411Problem 411Solution 420Consequences 424

Dependency Injection Pattern 426Problem 426Solution 428Constructor-Based vs. Property-Based Injection 429What Service Implementation to Inject 430Consequences 431Refactoring to DI 434

Refactoring to Patterns and Rent-a-Wheels Application 434Eliminating Code That Duplicates Functionality Available in .NET Framework 434Injecting Data Classes to GUI Classes via Dependency Injection 435CRUD Persistence Pattern 437

Summary 438

79796ftoc.qxd:WroxPro 2/23/08 8:45 AM Page xv

Page 19: Professional Refactoring in Visual Basic

xvi

Contents

Chapter 15: LINQ and Other VB 2008 Enhancements 439

Type Inference for Local Variables 439XML Productivity Enhancements 440

XML Literals 440Navigating XML with XML Axis Properties 444Extract XML Literal to Resource in Refactor! 444

Querying the Objects with LINQ 445Old Example in New Robes 448Object-Relational Mapping with LINQ to SQL 451LINQ and the Rent-a-Wheels Application 454

Summary 464

Chapter 16: The Future of Legacy VB Code 465

To Migrate or Not To Migrate 466Migration Cannot Be 100 Percent Automated 467VB 6 and VB .NET Code Can Interoperate 467Migration Tools and Libraries 469

Preliminary VB 6 Refactorings 470Breaking the Monolith 470Dealing with Conditional Compilation 472

Putting Your Migrated Code under a Testing Harness 472Introducing a Functional Testing Harness 472Implementing a Functional Testing Harness 473

Upgrading Your Legacy Code 477Strict Static Typing 477Moving Design from Procedural toward an Object-Oriented Paradigm 477Introducing Inheritance 478Making Use of Parameterized Constructor 479Using Generic Containers for Additional Type Safety 479Upgrading Exception Handling 481Implementing XML Comments 481Releasing Resources in .NET 482

Summary 482

Appendix A: Unleash Refactor! 483

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies 487

Hand Over Button Click Event-Handling Code 487Receive Button Click Event-Handling Code 488

79796ftoc.qxd:WroxPro 2/23/08 8:45 AM Page xvi

Page 20: Professional Refactoring in Visual Basic

xvii

Contents

Charge Button Click Event-Handling Code 488Change Branch Button Click Event-Handling Code 489To Maintenance and From Maintenance Button Click Event Code 493Administer Fleet Form 494

Delete Button Click Event-Handling Routine 495New Button Click Event-Handling Routine 495Reload Button Click Event-Handling Routine 495Form Load Event-Handling Routine 495Administer Fleet Form Class Code: Fields 498Left Button Click Event-Handling Routine 499Save Button Click Event-Handling Routine 500

Display Button Click Event-Handling Routine 502Summary 506

Index 507

79796ftoc.qxd:WroxPro 2/23/08 8:45 AM Page xvii

Page 21: Professional Refactoring in Visual Basic

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xviii

Page 22: Professional Refactoring in Visual Basic

Foreword

“Premature optimization is the root of all evil.”

— Sir Charles Antony Richard Hoare, a British computer scientist, later paraphrased by Donald Knuth in his book, The Art of Computer Programming

Days ago, watching a documentary about the life of South American movie director Fabián Bielinsky, I paidparticular attention to a thought he shared during an interview. He said that, when filming scenes, it usuallyhappens that a given scene isn’t taped as the director originally assumed it would be. Sometimes that sceneis reworked until the director gets what was originally wanted, while on some other occasions it’s just leftthe way it came, as it’s considered exponentially better than the initial sketch. So he concluded that the realart of making movies is deciding wisely when to do it again and when to just take what was gotten the firsttime around.

Surprisingly, if that is the case for making movies, coding components is very similar to taping scenes.It’s always possible to get better approaches for a given algorithm. So, with the manager hat on ourheads, we must decide when to freeze an always possible improvement for the sake of the timeline,budget, delivery due dates, and general customer satisfaction, and when to go on and get more.

So here is where we address the need for refactoring. Let’s start by defining — informally, as the author willdo it better ahead — what that means in software industry terms: refactoring is a series of techniques andmechanisms used to improve the quality — understandability, maintainability, modularity, extensibility, andso on — of code segments by reformulating their sentences in such a way that the general behavior remainsunchanged. In other words, the behavior of the affected components shouldn’t vary as a consequence of theprocess but their quality, and hopefully their longevity, should be increased.

Experience shows us that some portions of our code will sooner or later be candidates for refactoring,and the reasons are numerous:

❑ From the user side, users aren’t completely sure about the exact application they want until theysee installed and running what they asked for us originally. That’s not a joke! In the beginning,they start requiring something they envision, but vaguely, and that’s natural — I’m by no meansaccusing them. An undesirable side effect of this back and forth is that our code could start los-ing a certain degree of cohesion; its different modules may start being coupled above the accept-able levels, as a consequence of last-minute changes because of time-to-market pressure.

❑ From our own perspective, the development side, we don’t have a clear idea either about howcoding will look while we are modeling. As do the users, we also think we have cool ideas (or justgood ideas at the end) until we try to put some of them into practice. Failing isn’t bad. What’s bad is refusing to change our minds just to avoid admitting that what we had considered a great ideawasn’t, in fact, so easy to implement. And here, again, when time adds its pressure, delivering acomponent code the quickest we can may also harm its quality.

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xix

Page 23: Professional Refactoring in Visual Basic

❑ From the technology perspective, finally, there’s an invisible, somewhat omnipresent pressureto go along with industry trends. Typical examples are evolved .NET or Java APIs — AJAX,Web Services, and so on — that make former versions, or general strategies like Service-Oriented Architecture (SOA), Model-View-Controller (MVC), Object/Relational Mapping(O/R-M), and so on, obsolete. Once again, while reacting to those trends, our existing code is subject to some handling that, as time goes by, may erode its quality.

In the meantime, the real world shows us that making an effort to improve the quality of our code is something we innately tend to do. Considering that, refactoring techniques are nothing but thehighest degree of maturity of such spontaneous attempts, reinforced with some supporting tools available out there to guarantee the success of the process.

The good news about this is that we may apply refactoring locally, at a component or method level, therewhere we are applying any other modification; or globally, at a module or application level, assigning it a range of the project. Deciding on the right refactoring dose will depend on the quality gap to close, theremaining time, the available budget — it will always be easier to justify the application of refactoringwhere we already have to do something else than where no updates have yet to be asked for — amongother factors.

In this book, the author will address refactoring topics from the envisioning of their benefits to the current ways of putting refactoring into practice. Danijel Arsenovski has been involved in refactoringtechniques, both in the .NET and Java platforms, since their earliest versions. He has delivered speechesat several conferences, given talks, and held workshops on this subject, and driven successful refactor-ing projects in the banking industry.

As one of the leaders in development tools, Microsoft has been committed to delivering best-of-breedresources to the people who deal daily with coding activities and software projects as a whole. Throughits undisputably winning Visual Studio IDE, Microsoft makes refactoring an out-of-box facility just aright-click away from your code. In these pages, Danijel will show you how refactoring may be practicedin Visual Basic as easily as you do copy-paste or any other editing activities!

I dare tell you, dear reader, that you have one of the most proven and fundamental guides on these techniques. Enjoy reading this book!

Diego DagumTechnical Evangelist, Microsoft Corp.Kingsgate, Winter 2007

Foreword

xx

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xx

Page 24: Professional Refactoring in Visual Basic

Introduction

Thank you for choosing this book and welcome to the fabulous world of refactoring. I hope you will findthis book useful as you go about your daily programming chores, as you discuss different design solutionswith your peers, when you are getting ready to attack some obscure legacy code, and even as you are goingover some lines in your mind that are keeping you awake at night. If this is your first encounter with refac-toring, I expect this book to profoundly change the way you program and think about code. This is not aneasy task to complete, and, ultimately, you will be the judge of how successful I was.

I adopted refactoring in a systematic manner after I read the book Refactoring: Improving the Design ofExisting Code by Martin Fowler (Addison-Wesley, 1999). This book proved to be down-to-earth practical,helping me learn some indispensable techniques that I could apply in real-life projects right away. Thebook was not based on some complex theory, nor did it contain any complex mathematical formulas. It spoke in language immediately understood by anyone writing the code. Soon after I read the book, I noticed a number of changes in the way I program:

❑ I was able to detect with much greater certainty problematic code and design flows.

❑ I was able to think of solutions for those problems and resolve them effectively through refactoring.

❑ When talking to my peers, I was able to argue for my decisions in a clear and concise manner.

Finally, I stopped looking at the code as some solid structure constant in time, and started viewing it as aplastic, moldable form that I can fashion to my liking and in accordance with my needs. This provoked a fundamental change in the way I treat code. I realized that there is a way to modify code in an efficient,predictable manner and improve its design in the process.

Soon, the word about refactoring started to spread inside the team I worked with, and I saw more andmore of my coworkers taking the book from the shelf. A few even got their own copies. I was able tospeak with them using refactoring terminology and introduce refactoring as an integral part of the soft-ware construction process. Even the management proved to be forward-looking in this respect.

Partly because of my own interest in learning different languages and technologies, and partly because ofthe necessities that I am often presented with at my workplace, I get to work with different teams and pro-gram in different languages. I tried disseminating refactoring when working with teams that program inVisual Basic in a similar manner I did with Java or C# teams. This proved to not go that smoothly. Soon Irealized there is very little information on refactoring available for Visual Basic programmers. While mostof the refactorings can be applied in a similar manner in any object-oriented programming language, thereare some subtle differences.There is no reason why a programmer should not learn to refactor by lookingat code examples written in his language of preference.

This inspired me to think more about refactoring in Visual Basic and finally to write this book. I amconvinced that there is a real need for such a text and that this book will be of practical use to manywho program in Visual Basic.

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xxi

Page 25: Professional Refactoring in Visual Basic

I hope this book will help you write better code faster. I hope it will increase your productivity and have a positive impact on your coding performance. I expect that as a result you will create better, moresophisticated designs. Most importantly, I hope it will ease the burden of everyday tasks and put somefun back into what we all like to do best: program some great code.

Whom This Book Is ForThis book is intended for experienced (intermediate to advanced) Visual Basic .NET developers thatwish to be introduced to the world of refactoring. To get the most out of the book, you should have agood command of Visual Basic .NET and especially object-oriented programming in general.

If you are a beginning programmer, you will not be able to use this book as your primary source. Thisbook will not teach you the basics of programming in Visual Basic .NET. There is no reason, however,not to get acquainted with refactoring as early in your career as possible. As you learn to program yourfirst classes, you can use this book to learn how to design them properly and how to correct any mis-takes you might have introduced into your design.

If you are a VB 6 programmer, then you will not be able to successfully apply many of the refactoringsthat I deal with in this book because of differences between Visual Basic 6 (or previous) programminglanguages and Visual Basic .NET. In the case, however, that you are trying to upgrade your VB 6 code to VB .NET, you might find this book of great use. I have dedicated a whole chapter, the last one in thebook, to the subject of upgrading VB 6 code to .NET. The chapter itself, however, builds upon materialexposed earlier on in the book.

You might also find this book very useful as you are making the transition as a programmer from VB 6 toVisual Basic .NET. This book exposes some of the common mistakes that newcomers from VB 6 to VB .NETmake and teaches you how to deal with those mistakes.

This book makes no assumption on the type or domain of your application. It can be a typical web appli-cation, a web service, a framework, a component, a shopping cart application, a new Facebook widget,or a shooter game, but whatever it is, as long there is some VB code in it, you will find the techniquesexplained in this book valuable.

Most of the refactorings I deal with in this book are standard refactorings applicable in any fully object-oriented language. This means that if you program in some other object-oriented language, for exampleC#, as long as you are familiar with VB .NET syntax and you are able to read code examples, you will beable to use and apply the information exposed in this book.

What This Book CoversI have tried to make this book a good introduction and thorough overview of refactoring in Visual Basic.This book teaches the basic refactoring concepts:

❑ Code Smells

❑ Refactoring code transformations

Introduction

xxii

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xxii

Page 26: Professional Refactoring in Visual Basic

❑ Some basic object-oriented principles

❑ The use of a refactoring tool that can automate the refactoring process

The book uses a single case study, relatively large in size for a book case study, to demonstrate practical,real-life application of refactoring over a realistically large code base.

In addition to standard refactorings applicable to any object-oriented language, this book teaches VB .NET–specific refactorings. It also demonstrates some special uses of refactoring. For example:

❑ Refactoring as an integral part of upgrade of VB 6 code to VB .NET

❑ Refactoring for upgrade of VB .NET 2005 code to VB .NET 2008

❑ Refactoring for implementation of design patterns

This book contains a good number of important smells and refactorings. However, this book does notrepresent a complete refactoring catalog; because of time and space limitations, some important refactor-ings had to be excluded. For example, it does not deal with refactorings such as Simplify Conditional orReverse Conditional, already available for automation in the Refactor! add-in from Developer Express. It also does not deal with many “reverse” refactorings, like Inline Method or Inline Class.

This book is intended first of all to be an introduction to refactoring. My experience tells me that the first problem that programmers deal with when starting out on the refactoring path is poorly structuredcode, and refactorings such as Extract Method or Extract Class deal with this problem. Their opposites,Inline Method and Inline Class refactorings, help you deal with excessively structured code. They willaid you in eliminating constructs (methods or classes, for example) that are not needed any more. Thisoften happens after extensive refactoring is applied to the code base.

This does not mean that “reverse” refactorings are less important. The need for them is more likely to appearlater on in the refactoring acquisition, once you have already grasped the basics of refactoring. Part of thelearning process is to understand that the knowledge acquisition process never really ends. For example,with each new version of the Refactor! add-in, new refactorings are added to the battery of supported refac-torings. People invent and devise new refactorings continuously. As you become proficient in the technique,you will invent your own refactorings and might eventually decide to share them with others.

So I invite you to go beyond this book; don’t feel limited by the refactorings listed in this book. Look for andtry to invent new refactorings. This way, you will truly master the art of continuous code improvement.

How This Book Is StructuredBecause this is the first book that deals exclusively with refactoring in Visual Basic and will probably bethe first book on refactoring for the reader, I hope this book will fulfill more than one purpose:

❑ Be a thorough introduction to refactoring in Visual Basic.

❑ Be a thorough reference on refactoring techniques and code smells that can be consulted duringeveryday programming sessions.

❑ Demonstrate how refactoring techniques can be applied in a real-life situation, by means of singlecase study, the Rent-a-Wheels application, that I analyze and modify at the end of each chapterthroughout the book.

Introduction

xxiii

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xxiii

Page 27: Professional Refactoring in Visual Basic

In order to accomplish that, the main book narrative reads just like those of any other technical books.New concepts are introduced and elaborated in logical progression from more basic to more complex.Simplified, illustrational code examples are given for each concept. This way, you can read this book inlogical order from beginning to end. This is something that you should probably do once or twice afteryou acquire this book and open it for the first time.

In addition to this main narrative, you will see that the book is sprinkled with so called “definition boxes” ofsmells, refactorings, and object-oriented design principles. The purpose of these definitions is to give a con-densed overview of the subject. In the case of smells for example, this definition contains heuristics on howthe smell can be discovered. In the case of refactorings, there is a section called “Mechanics” that contains aform of recipe and describes the steps you need to perform in order to execute certain refactoring effectively.You should be able to consult these during your everyday work, to remind yourself how the refactoring isperformed, how a smell is discovered, and what refactoring can be used to eliminate it, and so on.

At the end of the majority of chapters, you will find the discussion of how refactorings, smells, and princi-ples I talked about in the chapter reflected on the case study included with the book. You can downloadthe code for the chapter and browse it as you read about what happened to the case study in the currentchapter. The purpose of the case study is to present you with a more life like application of refactoring.Often in technical books you will find a selection of code samples that have been unrealistically simplifiedin order to prove point. While this makes the examples clearer and easier for the writer to prove the point,it often means the reader will confront much more complex situations in real life, with many unexpectedobstacles appearing if certain techniques are to be applied to production code. In this book, I tried to pres-ent reader with a much more realistic scenario by means of the “Rent-a-Wheels” study case.

The book is divided into five major parts that lead the reader from more basic to more complex conceptsin logical progression.

❑ Part I, “Introduction to Refactoring,” lays the foundation for the book. For example, in Chapter 1I talk about refactoring in some general terms. This chapter also dispels some common misconcep-tions about refactoring. Chapter 2 gives you a taste of refactoring in practice right away. ThenChapter 3 goes over the tools that you will find indispensable in order to automate your refactor-ing work. Chapter 4 presents you with a case study used throughout the book.

❑ Part II, “Preliminary VB Refactorings,” covers some preliminary refactorings such as deadcode elimination and others that will help you prepare the code for major overhaul. You dealwith some VB-only problems such as strict versus weak typing, legacy versus structured errorhandling, and so on.

❑ Part III, “Getting Started with Standard Refactoring Transformations,” deals with some standard,core refactorings. You learn about the importance of choosing names for your code constructs andabout the devastating effects of duplicated code. You deal with standard refactorings such asExtract Method and go into the details of code structuring on a method level.

❑ Part IV, “Advanced Refactorings,” talks about some more advanced refactorings that let youget the most from object-oriented capabilities of your programming environment. Good object-oriented skills are essential for this part of the book. You will learn how to discover classes, create inheritance hierarchies, and reorganize your code on a large scale.

❑ Part V, “Refactoring Applied,” shows you how refactoring can be successfully applied in order to reach a more specific goal. For example, you will see how refactoring can be paired with designpatterns to produce even more sophisticated design. You will see some of the new features thatcome with the Visual Studio 2008 version of VB and how refactoring can be used to get the mostout of them. Finally, you will take a look back at VB 6, a Visual Basic .NET predecessor. You willlearn how to apply refactoring in order to update your VB6 code to Visual Basic .NET.

Introduction

xxiv

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xxiv

Page 28: Professional Refactoring in Visual Basic

What You Need to Use This BookIn order to successfully use this book, you will need the following software:

❑ Visual Studio .NET 2008 — You will need at least the Professional Edition in order to be able touse the Refactor! add-in. You will still be able to debug, execute code, and perform refactoringsmanually in the free Visual Basic 2008 Express Edition. If you use Visual Studio 2005, you willalso be able to get some use out of most of the book, except for Chapter 15, which deals exclu-sively with features available in Visual Basic 9 and Visual Studio 2008 only.

❑ Refactor! for VB add-in from Developer Express — Download the latest version of this freeVisual Studio add-in from the Developer Express site: www.devexpress.com/vbrefactor/.

❑ Microsoft SQL Server — You will need this in order to run the sample case study included in thebook. This can be MS SQL Server 2000 or 2005, and the Express edition will suffice for the appli-cation that comes with this book. You can use any operating system, like Windows XP, WindowsServer 2003, or Windows Vista, that can run the software I just listed.

ConventionsTo help you get the most from the text and keep track of what’s happening, a number of conventions areused throughout the book.

Tips, hints, tricks, and asides to the current discussion are offset and placed in italics like this.

As for styles in the text:

❑ New terms and important words are highlighted when introduced.

❑ Keyboard strokes look like this: Ctrl+A.

❑ File names, URLs, and code within the text look like this: persistence.properties.

❑ Code is presented in two different ways:

Monofont type with no highlighting is used for most code examples.Gray highlighting is used to emphasize code that’s particularly important in thepresent context.

❑ Also, bold is occasionally used within code listings to emphasize parts of code.

Boxes like this one hold important, not-to-be forgotten information that is directlyrelevant to the surrounding text. This information includes some important defini-tions you encounter throughout the book. You might also look for these definitionswhen using the book in a reference-like manner.

Introduction

xxv

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xxv

Page 29: Professional Refactoring in Visual Basic

Smell, Refactoring, and Object-OrientedDesign Principle Boxes

In addition to the conventions just mentioned, throughout the course of this book you will come acrossthree other types of boxed text: Smells, Refactorings, and Object-Oriented Design Principles.

❑ Smells — These boxes contain condensed definitions of code smells. A code smell is an impor-tant refactoring concept, and each box contains three sections:

❑ Detecting the Smell — Describes some simple heuristic on how to detect the code smellin your code.

❑ Related Refactorings — Lists the refactorings you can use in order to eliminate the smell.

❑ Rationale — Explains the negative effect of the code smell in more theoretical terms.

❑ Refactorings — The content these boxes gives comprises the bare basics of the refactoring. Eachbox contains the following sections:

❑ Motivation — Explains the beneficial effect this refactoring has on code and how thedesign is improved with its application.

❑ Related Smells — Lists the smells this refactoring can help you eliminate.

❑ Mechanics — Gives a step-by-step recipe on how to perform the refactoring.

❑ Object-Oriented Design Principles — In these boxes I define some of the crucial object-orienteddesign principles and often use some short code samples to illustrate them.

Index of SmellsFor ease of reference, the following table indicates what smells are included in the book and the pagenumber where the box dealing with that specific smell can be found.

Smell Page Number

Comments 226

Cyclic Dependencies 384

Data Class 286

Database-Driven Design 288

Dead Code 174

Duplicated Code 235

Error Code 154

Event-Handling Blindness 232

Implicit Imports 372

Introduction

xxvi

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xxvi

Page 30: Professional Refactoring in Visual Basic

Index of RefactoringsThe following table indicates what refactorings are included in the book and the page number where thebox dealing with that specific refactoring can be found.

Continued

Refactoring Page Number

Break Monolith 471

Smell Page Number

Implicit Narrowing Conversions 125

Implicit Type Declaration 118

Implicit Variable Declaration 113

Large Class 298

Large Namespace 377

Legacy (Unstructured Error Handling) 153

Long Method 225

Magic Literals 236

Monolithic Application 470

Non-Coherent Namespace 378

Overburdened Temporary Variable 249

Overexposure 179

Procedural Design 302

Refused Bequest 337

Superfluous Temporary Variable 254

Unrevealing Names 201

Unused References 191

Using Fully-Qualified Names Outside Imports Section 188

XML String Literals 441

Introduction

xxvii

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xxvii

Page 31: Professional Refactoring in Visual Basic

Refactoring Page Number

Convert Procedural Design to Objects 307

Eliminate Dead Code 176

Encapsulate Field 275

Explicit Imports 374

Extract Class 281

Extract Interface 346

Extract Method 221

Extract Namespace 382

Extract Super Class 352

Inline Temp 254

Move Class to Namespace 380

Move Declaration Near Reference 245

Move Element to a More Enclosing Region 185

Move Field 291

Move Initialization to Declaration 248

Move Method 289

Move Type to File 388

Pull Up Method 357

Reduce Access Level 182

Remove Unused References 192

Rename 206

Replace Complex VB Queries with LINQ 447

Replace Error Code with Exception Type 164

Replace Fully-Qualified Names with Explicit Imports 188

Introduction

xxviii

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xxviii

Page 32: Professional Refactoring in Visual Basic

Index of Object-Oriented Design PrinciplesThe following table indicates the object-oriented design principles that are included in the book and thepage number where the box dealing with that specific principle can be found.

Object-Oriented Design Principle Page Number

Acyclic Dependencies Principle 387

Favor Object Composition over Class Inheritance 331

Open-Closed Principle 215

Program to an Abstraction 331

Reuse-Release Equivalence 378

Single Responsibility 294

Refactoring Page Number

Replace General-Purpose Reference with Parameter Type

361

Replace Inheritance with Delegation 338

Replace Magic Literal with Constant 237

Replace “On Error” Construct with Try–Catch–Finally 237

Replace Programmatic Data Layer with LINQ to SQL 453

Replace Row with Data Class 299

Replace Temp with Query 257

Replace XML String Literals with XML Literal 442

Safe Rename 210

Set Option Explicit On (Enforce Variable Declaration) 113

Set Option Strict On (Enforce Variable Type Declarationand Explicit Type Conversions)

131

Split Temporary Variable 251

Introduction

xxix

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xxix

Page 33: Professional Refactoring in Visual Basic

Source CodeAs you work through the examples in this book, you may choose either to type in all the code manuallyor to use the source code files that accompany the book. All of the source code used in this book is avail-able for download at http://www.wrox.com. Once at the site, simply locate the book’s title (either byusing the Search box or by using one of the title lists) and click the Download Code link on the book’sdetail page to obtain all the source code for the book.

Because many books have similar titles, you may find it easiest to search by ISBN; this book’s ISBN is978-0-470-17979-6.

Once you download the code, just decompress it with your favorite compression tool. Alternately, youcan go to the main Wrox code download page at http://www.wrox.com/dynamic/books/download.aspx to see the code available for this book and all other Wrox books.

ErrataWe make every effort to ensure that there are no errors in the text or in the code. However, no one is per-fect, and mistakes do occur. If you find an error in one of our books, like a spelling mistake or faulty pieceof code, we would be very grateful for your feedback. By sending in errata you may save another readerhours of frustration, and at the same time you will be helping us provide even higher quality information.

To find the errata page for this book, go to http://www.wrox.com and locate the title using the Searchbox or one of the title lists. Then, on the book details page, click the Book Errata link. On this page you canview all errata that has been submitted for this book and posted by Wrox editors. A complete book listincluding links to each book’s errata is also available at www.wrox.com/misc-pages/booklist.shtml.

If you don’t spot “your” error on the Book Errata page, go to www.wrox.com/contact/techsupport.shtml and complete the form there to send us the error you have found. We’ll check the informationand, if appropriate, post a message to the book’s errata page and fix the problem in subsequent editionsof the book.

p2p.wrox.comFor author and peer discussion, join the P2P forums at p2p.wrox.com. The forums are a web-based systemfor you to post messages relating to Wrox books and related technologies and interact with other readersand technology users. The forums offer a subscription feature to e-mail you topics of interest of your choos-ing when new posts are made to the forums. Wrox authors, editors, other industry experts, and your fellowreaders are present on these forums.

At http://p2p.wrox.com you will find a number of different forums that will help you not only as youread this book, but also as you develop your own applications. To join the forums, just follow these steps:

1. Go to p2p.wrox.com and click the Register link.

2. Read the terms of use and click Agree.

Introduction

xxx

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xxx

Page 34: Professional Refactoring in Visual Basic

3. Complete the required information to join as well as any optional information you wish to pro-vide and click Submit.

4. You will receive an e-mail with information describing how to verify your account and com-plete the joining process.

You can read messages in the forums without joining P2P but in order to post your own messages, youmust join.

Once you join, you can post new messages and respond to messages other users post. You can read mes-sages at any time on the Web. If you would like to have new messages from a particular forum e-mailedto you, click the Subscribe to this Forum icon by the forum name in the forum listing.

For more information about how to use the Wrox P2P, be sure to read the P2P FAQs for answers to ques-tions about how the forum software works as well as many common questions specific to P2P and Wroxbooks. To read the FAQs, click the FAQ link on any P2P page.

Introduction

xxxi

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xxxi

Page 35: Professional Refactoring in Visual Basic

79796flast.qxd:WroxPro 2/25/08 9:23 AM Page xxxii

Page 36: Professional Refactoring in Visual Basic

Part I: Introduction to Refactoring

In this introductory part, you are going to see what refactoring is in general terms, whyit is important, what benefits refactoring brings to the development process, and how itcan be even more relevant to Visual Basic programmers than to programmers in someother languages. You are also going to see a small demonstration of the refactoringprocess at work, explore the tools relevant to refactoring, and, finally, take a look at a sample application I will use throughout this book to illustrate refactorings and therefactoring process as it is applied.

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 1

Page 37: Professional Refactoring in Visual Basic

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 2

Page 38: Professional Refactoring in Visual Basic

Refactoring: What’s All the Fuss About?

Take a look at any major integrated development environment (IDE) today and you are bound todiscover “refactoring” options somewhere at the tip of your fingers. And if you are followingdevelopments in the programming community, you have surely come across a number of articlesand books on the subject. For some, it is the most important development in the way they codesince the inception of design patterns.

Unlike some other trends, refactoring is being embraced and spread eagerly by programmers andcoders themselves because it helps them do their work better and be more productive. Without adoubt, applying refactoring has become an important part of programmers’ day-to-day labor nomatter the tools, programming language, or type of program being developed. Visual Basic is apart of this: at this moment, the same wave of interest for refactoring in the programming commu-nity in general is happening inside the Visual Basic community.

In this introduction,

❑ I start out by taking a look at what refactoring is and why it is important and then discussa few of the benefits that refactoring delivers.

❑ I also address some of the most common misconceptions about refactoring.

❑ In the second part of this chapter, I want you to take a look at the specifics of Visual Basicas a programming language and how refactoring can be even more relevant for VisualBasic programmers because of some historic issues related to Visual Basic.

I’ll start with some background on refactoring in general.

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 3

Page 39: Professional Refactoring in Visual Basic

A Quick Refactoring OverviewWhen approaching some programming task, you have a number of ways in which you can go about it.You start off with one idea, but as you go along and get into more detail, you inevitably question yourwork along these lines: “Should I place this method in this class or maybe in this other class? Do I need a class to represent this data as a type or am I well off using the primitive? Should I break this class intomore than one? Is there an inheritance relationship between these two classes or should I just use com-position?” And if you share your thoughts with some of your peers, you are bound to hear even moreoptions for designing your system. However, once you commit yourself to one approach, it may seemvery costly to change these initial decisions later on. Refactoring teaches you how to efficiently modifyyour code in such a way that the impact of those modifications is kept at a minimum. It also helps youthink about the design as something that can be dealt with at any stage of the project, not at all cast instone by initial decisions. Design, in fact, can be treated in a very flexible way.

All design decisions are the result of your knowledge, experience, and creativity. However, programmingis a vast playfield, and it’s easy to get tangled in contradictory arguments. In VB .NET you are, first andforemost, guided by object-oriented principles and rules. Unfortunately, very often it is not so clear howthese rules work out in practice. Refactoring teaches you some simple heuristics that can help improveyour design by inspecting some of the visible characteristics of your code. These guidelines that refactor-ing provides will set you on the right path in improving the design of your code.

The Refactoring ProcessRefactoring is an important programming practice and has been around for some time. Pioneered by theSmalltalk community, it has been applied in a great number of programming languages, and it has takenits place in many programmers’ bags of tricks. It will help you write your code in such a way that youwill not dread code revision. Being a programmer myself, I know this is no small feat!

So, how do you perform refactoring? The refactoring process is fairly simple and consists of three basic steps:

1. Identify code smells.

You’ll see what code smell means very soon, but, in short, this first step is concerned with identi-fying possible pitfalls in your code, and code smells are very helpful in identifying those pitfalls.

2. Apply the appropriate refactoring.

This second step is dedicated to changing the structure of your code by means of refactoring trans-formations. These transformations can often be automated and performed by a refactoring tool.

3. Execute unit tests.

This third step helps you rectify the state of your code after the transformations. Refactoring is not meant to change any behavior of your code observable from the “outside.” This step generallyconsists of executing appropriate unit tests that will prove the behavior of your code didn’t changeafter performing refactoring.

Definition: Refactoring is a set of techniques used to identify the design flows and tomodify the internal structure of code in order to improve the design without changingcode’s visible behavior.

4

Part I: Introduction to Refactoring

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 4

Page 40: Professional Refactoring in Visual Basic

You might have noticed the word design used in the refactoring definition earlier in the chapter. This is a broad term and can take on very different meanings depending on your background, programmingstyle, and knowledge. Design in this sense simply means that refactoring builds upon object-orientedtheory with the addition of some very simple heuristics dedicated to identifying shortcomings and weakspots in your code. These antipatterns are generally referred to as code smells and a great part of refactor-ing can be seen simply as an attempt to eliminate code smells.

The code smell can be something as simple as a very large method, a very large class, or a class consistingonly of data and with no behavior. I’ll dedicate a lot of time to code smells in the book, because improvingyour sense of code smell can be very important in a successful refactoring process.

The aim of refactoring is to improve the design of your code. You generally do this by applying modifi-cations to your code. The refactoring methodology and its techniques help you in this task by making iteasier to perform and even automate such modifications.

A Look at the Software SituationAs software developers, your success depends on being able to fulfill different types of expectations. Youhave to keep in mind many different aspects of your development work; here are just a few of the concerns:

❑ Very often you will hear that the most important one is satisfying user requirements, generallymeaning that you should create software that does what the client paid for.

❑ You also need to guarantee the quality of your product. You strive to reduce defects and torelease a program that has the minimum number of bugs.

❑ You have to think about usability, making programs that are easy to understand and exploit.

❑ You tend to be especially concerned about performance, always inventing new ways to minimizememory usage and the number of cycles needed in order to solve some problem.

❑ You need to do all of this in a timely manner, so you are always looking for ways to augment productivity.

These issues cause us to focus, and rightly so, on the final product (also known as the binary) and how itwill behave for the final user. However, in the process of producing the binary, you actually work withsource code. You create classes, add properties and methods, organize them into the namespaces, write logicusing loops and conditions, and so on. This source code, at a click of a button, is then transformed, com-piled, or built into a deliverable, a component, an executable, or something similar. There is an inevitablegap between the artifacts you work on — the source — and the artifacts you are producing — the binary.

In a way, this gap is awkward and not so common in the other areas of human activity. Take a look atstonemasonry, for example. While the mason chips away pieces of stone and polishes the edges, he orshe can see the desired result slowly appearing under the effort. With software, the process is not at allas direct. You write source code that is then transformed into the desired piece of software. Even withthe visual tools, which largely bridge this gap between source and binary, all you do in the end is create

Definition: Code smell is a sensation you develop that tells you that there might be aflaw in your code.

5

Chapter 1: Refactoring: What’s All the Fuss About?

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 5

Page 41: Professional Refactoring in Visual Basic

the source that is later on processed and turned into a compiled unit. Imagine a cook that can only writedown a recipe and try the cooked meal, but is not allowed to handle the ingredients or taste the mealwhile it is being prepared.

What’s more, there are many ways to write the source that will produce the same resulting binary. This caneasily lead you to forget or sacrifice some qualities inherent to the source code itself, because the sourcecode can be considered just a secondary artifact. While these qualities are not directly transformed to a finalproduct, they have an immense impact on the whole process of creation and maintenance.

This leads to the following question: Can we distinguish between well written and poorly written code,even if the final result is the same? In the following sections, I’ll explore this question, and you’ll seehow refactoring can clarify doubts you might have.

Refactoring Encourages Solid DesignNo matter your previous programming experience, I am certain you will agree that you can indeed distinguish between good and bad code.

Assessing code may begin on a visual level. Even with a simple glance you can see if the code is indentedand formatted so it is pleasing to view, if the agreed naming conventions are used, and so on.

At a less superficial level, you start to analyze code according to principles and techniques of softwaredesign. In Visual Basic, you follow the object-oriented software paradigm. You look into how well classesare structured and encapsulated, what their responsibilities are, and how they collaborate. You use lan-guage building blocks like classes and interfaces; and features like encapsulation, inheritance, and poly-morphism in building a cohesive structure that describes the problem domain well. In a certain way youbuild your own ad-hoc language on top of a common language that will communicate your intentionsand design decisions.

There are a number of sophisticated principles you need to follow in order to achieve a solid design.When you create software that is reusable, extendible, and reliable, and that communicates its purposewell, you can say you have reached your goal of creating well-designed code.

Refactoring gives you a number of recipes to ensure that your software conforms to the principles ofwell-designed code. And when you stray from your path, it helps you reorganize and impose the bestdesign decisions with ease.

Refactoring Accommodates Change Popular software design techniques like object-oriented analysis and design, UML diagramming, use-case analysis, and others often overlook one very important aspect of the software creation process: constant change. From the first moment it is conceived, software is in continuous flux. Every so often,requirements will change even before the first release, new features will be added, defects corrected, andeven some planned design decisions, when confronted with real-world demands, overruled. Softwareconstruction is a very complex activity, and it is futile attempting to come up with a perfect solution upfront. Even if some more sophisticated techniques like modeling are used, you still come short of think-ing about every detail and every possible scenario. It is this state of flux that is often the biggest chal-lenge in the process of making software. You have no choice but to be ready to adapt, count on change,and react readily when it happens. If you are not ready to react, the design decisions you made are soonobscured, and the dangerous malaise of rotting design settles in.

6

Part I: Introduction to Refactoring

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 6

Page 42: Professional Refactoring in Visual Basic

Refactoring is a relatively simple way to prepare for change, implement change, and control the adverseeffects these changes can have on your design.

Refactoring Prevents Design RotSoftware is definitely one of the more ephemeral human creations. Driven by new advances and technolo-gies, software creations are soon replaced with more modern or advanced versions. Even so, during itslifetime software will journey through a number of reincarnations. It is constantly modified and updated,new features are added and old ones removed, defects resolved and adaptations performed. It is quitecommon that more than one person will put their hands on the same piece of software, each with his orher own style and preferences. Rarely will it be the same team of people that will see the software fromthe beginning to the end.

Go back for a moment to the stonemason example. Now imagine that there is more than one personworking on the same stone, that these people can change during the collaboration, and that the originalplan is often itself changed with new shapes added or removed and different materials used. That maybe a task for somebody of Michelangelo’s stature, but definitely not for the ordinary craftsman.

No wonder then that initial ideas soon are forgotten, thought-out structure superseded by new solu-tions, and original design diluted. The initial intentions become less pronounced and the metaphorsmore difficult to comprehend, and the source is closer and closer to a meaningless cluster of symbolsthat still, but a lot less reliably, performs the intended function. This ailment steals in quietly, step bystep, often unnoticed, and you end up with source that is difficult to maintain, modify, or upgrade.

What I’ve just described are the symptoms of rotting design, something that can occur even before thefirst release lives to see the light of a day. Refactoring helps you prevent design rot.

So, as you have moved along in this brief survey of the software landscape, I’ve pointed out several chal-lenges that developers face and how refactoring can help. Next, I want to discuss refactoring in more detail.

The Refactoring Process: A Closer LookI just discussed a few key areas of software development that can often lead to poor code. You need tostand guard for the quality of your code constantly. In effect, you need to have the design qualities ofyour code in mind at all times.

While this sounds sensible, thinking continuously about design and code quality can often be costly andquite complicated. The refactoring methodology and its techniques help you in this task by making iteasier to perform and even automate modifications that will keep the design active.

In this section I’m going to take a look at the refactoring activities you would typically complete duringa software development cycle.

Using Code SmellsAs a first step in your refactoring activity, you take a look at the code in order to assess its design qualities.Refactoring teaches you a set of relatively simple heuristics called code smells that can help you with thistask, along with well-known notions and principles of object-oriented design. Programming, being complex

7

Chapter 1: Refactoring: What’s All the Fuss About?

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 7

Page 43: Professional Refactoring in Visual Basic

as it is, makes it difficult to impose precise rules or metrics, so these smells are more general guidelines and are susceptible to taste and interpretation. Along with gaining more experience and knowledge, youdevelop more expertise in identifying and eliminating bad smells in your code.

Transforming the CodeThe next step leads you to modifying the code’s internal structure. Here, refactoring theory has devel-oped a set of formal rules that enable you to execute these transformations in such a way that, for a client,these modifications are transparent. You do not have to tackle the theory behind these rules. The tool-makers use these rules to make certain that refactoring modifies the code in a predictable way.

For example, let me illustrate this modification that preserves the original behavior of the code with anexample. In Table 1-1, imagine you transformed the code at the left side into the code on the right side.

Table 1-1: Two Forms of Writing the Code that Will Execute in the Same Way

All you did here was to replace the literal value 1.5 with a constant OvertimeIndex. Executing thecode on both sides provides identical results, but the one on the right can be a lot easier to maintain ormodify. And it is definitely easier to understand. Now you understand that the literal 1.5 has a specialmeaning and has not been selected by chance.

Automating Refactoring TransformationsRefactoring rules have one great consequence: it is possible to automate a large number of these trans-formations. Automation is really the key to letting refactoring show its best. Refactoring tools will checkfor the validity of what you are trying to perform and let you apply a transformation only if it doesn’tbreak the code. Even without a tool, refactoring is worth your while; however, manual refactoring can beslow and tedious.

Free Literal Value Literal as Constant

Public Class Employee

Private hoursWorked As Integer

Private overtimeHoursWorked _As Integer

Private hourlyWage As Decimal

Public Function GetWage() _As Decimal

Return (hoursWorked * _hourlyWage) + _(overtimeHoursWorked * _hourlyWage _* 1.5)

End FunctionEnd Class

Public Class Employee

Public Const OvertimeIndex _As Decimal = 1.5

Private hoursWorked As Integer

Private overtimeHoursWorked _As Integer

Private hourlyWage As Decimal

Public Function GetWage() _As Decimal

Return (hoursWorked _*hourlyWage) + _(overtimeHoursWorked _* hourlyWage _* OvertimeIndex)

End FunctionEnd Class

8

Part I: Introduction to Refactoring

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 8

Page 44: Professional Refactoring in Visual Basic

Figure 1-1 shows the Refactor! for VB Visual Studio add-in from Developer Express integrated withVisual Studio 2005.

Figure 1-1

The Benefits of RefactoringIn light of all this, it is pertinent to ask what benefits refactoring brings. After all, it is not about addingnew features or resolving bugs, and you end up with code that basically does what it used to, so whyshould you invest the time and money to perform this activity? What are the benefits of keeping yourdesign optimal at all times? How does refactoring pay off?

Keeping the Code SimpleWith the fact that software development is a continuous, evolving process, refactoring can bring impor-tant qualities to your code. Keeping your code lean at all times can be challenging, especially whenyou’re under pressure to deliver the results quickly. So how does your code become overly complex?There are several ways this happens.

❑ In one typical scenario, you add a function here, a property there, another condition will cropup, and so on. This will soon produce a situation where classes and methods have grown andgone beyond their original purposes. They have too many responsibilities, communicate withmany other elements, and are prone to change for many different reasons. It also becomes abreeding ground for duplicated code.

9

Chapter 1: Refactoring: What’s All the Fuss About?

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 9

Page 45: Professional Refactoring in Visual Basic

❑ In another scenario, you start off with a very thorough design that proves to be more than youreally need. Simple code does only what it is supposed to do; you need not be so concernedmuch with trying to have your solution respond to any possible situation even before it hap-pens. You can easily develop a tendency to overengineer your code, using complex structureswhen simple ones will do. (You can easily identify this school of thought by how many “whatif” statements are used in discussions of the code.) This situation is motivated by an urge toanticipate future requirements even before they are expressed by the client.

❑ Performance has proved to be a lure for generations of programmers. You might spend numer-ous hours in order to obtain nanosecond gains in execution time. Without trying to lessen theimportance of this key quality of software, you should bear in mind the right moment to dealwith it. It can be very difficult to find the critical line you need to change in order to improveperformance even for systems already in production; there is even less of a probability that youcan find it while the system is in plain development and you are not sure what the rest of thepieces will end up looking like. Using the IDE as the performance-testing environment can beequally misleading.

How can you avoid such pitfalls? Once you become aware of them, you should deal with them quickly.Keeping things on the simple side will be greatly rewarded each time you need to add a feature, resolvea bug, or perform some optimization.

❑ If you see that a method has grown out of proportion, it is time to add a new method(s) that willtake off some of the burden.

❑ If a class has too many members, maybe it can be restructured into a group of collaboratingclasses or into a class hierarchy.

❑ If a modification left some code without any use and you are certain that it will never be executed,there’s no need to keep it; it should be eliminated.

All these solutions represent typical refactorings. After a smell is discovered, the solution is a restructuringof the problematic code.

When code is simple, it is easy to navigate — you don’t lose time in long debugging sessions in order to findthe right spot. The names of classes, methods, and properties are meaningful; code purpose is easy to grasp.This type of code won’t have you reaching for documentation or desperately searching through the com-ments. Even after a short time spent with such code, you feel it does not hide any major mysteries. In simplewords, you are in control.

Keeping the Code ReadableProgramming is intellectually a very intense activity. You are often so immersed in your work that youtend to have a deep and detailed understanding of your creation in order to maintain complete controlover it. You may try to memorize every single detail of the code. You feel proud when you are able toimmediately correct a bug or change some behavior. After all, it is what makes you good in the workyou perform. As you become more productive, you develop strategies and gain your own programmingstyle. There is nothing wrong with being expert with the code you create, unless that expertise becomesthe only weapon you have in your arsenal.

Unfortunately, sometimes you can forget one important fact; when developing software you seldomwork alone. And in order to be able to work in a team, you must write code so it is easily comprehended

10

Part I: Introduction to Refactoring

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 10

Page 46: Professional Refactoring in Visual Basic

by others. Others might need to modify, maintain, or optimize the code. In that case, if confronted withcryptic or hermetic code, others could lose numerous hours in a pure attempt to understand the code.Sooner or later you’ll have a computer do all your bidding, but until then, writing source in such waythat it is easy for others to understand can prove to be a much more difficult task. Ironically, you can findyourself in the “other person” role even with your own code. Your memory has its limits, and after awhile you may not be able to remember every detail of the code you yourself wrote.

Readability can depend on different factors. Visual disposition is easily corrected and standardized with the IDE. Other factors, like the choice of identifier names, require a carefully thought-out approach.Because programmers often come from different backgrounds and have different experiences, the bestbet is relying on natural language itself. You have to translate your decisions into code so they are easilyunderstandable from a reading of the code, not only visible as a consequence of code execution. Codebecomes really meaningful when a relation between it and a problem domain is correctly established.

As a programmer, you continuously develop your vocabulary. Using well-known idioms, patterns, andaccepted conventions can increase the clarity of your code.

Reliance on comments and documentation can also affect the capacity of code to communicate with thereader. Because these artifacts never get executed, they are the first to suffer from obsolescence. Secondly,they are notorious for containing superfluous information.

I will try to illustrate this with two code snippets that perform equally during execution. Try readingfirst the snippet on the left side in Table 1-2, and then the one on the right.

Table 1-2: An Example of Code that is Difficult to Read and the Same Code in a More Readable Form

If I have proved my point, you will find the second snippet more to your liking. In case you still are notconvinced, as an interesting experiment, you can try obfuscating your code with some obfuscation tooland then trying to find your way around it. Even with the smallest code base, it soon becomes impossibleto understand the code. No wonder, because obfuscation is a process completely opposite to refactoring.

Refactoring tools can help you improve readability by letting you rename identifiers in your code in asafe and systematic way and by letting you transform your code along well-known patterns andidioms — you use comments in a more profound manner. Strong structure in the code gives you confi-dence that the information you obtain from reading the code relates well to execution time.

All this sounds very good. However, you can often hear arguments against refactoring. While some of thosearguments are well founded, let me first deal with some opinions often heard that are not very constructive.

Difficult to Read More Readable Code

Dim oXMLDom As _New DOMDocument40Dim oNodes As IXMLDOMNodeList‘loads the file into XMLDom objectoXMLDom.Load(App.Path + _“\ portfl.xml”)oNodes = _oXMLDom.selectNodes(“//stock[1]/*“)

Dim portfolio As _New DOMDocument40Dim stocks As IXMLDOMNodeList

portfolio.Load(App.Path + _“\portfl.xml”)stocks = portfolio.selectNodes(“//stock[1]/*“)

11

Chapter 1: Refactoring: What’s All the Fuss About?

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 11

Page 47: Professional Refactoring in Visual Basic

Debunking Common MisconceptionsLike any topic that creates a huge amount of interest among developers, refactoring has produced an avalanche of opinions and contributions, some of more and others of less value. In certain cases Ifound those opinions so unfounded that I call them misconceptions. I feel it is worthwhile taking sometime to debunk them, because they can add confusion and can lead you astray from a quest to adoptthis valuable technique.

Refactoring Violates the Old Adage, “If It Ain’t Broke, Don’t Fix It”Often portrayed as longstanding engineering wisdom, this posture only promotes complacency.Refactoring does teach against it, but for a reason.

Early on you learn how even a minuscule detail in code can make all the difference, often paying dearlyfor this knowledge. A small change can provoke software to break in a surprising manner and at theworst moment. So once you burn your hands you often become reluctant to make any change that is notabsolutely necessary. This can work well for a moment, but then a situation comes up where bugs haveto be resolved and petitions for new features cannot be evaded anymore. You are faced with the samecode you tried not to confront.

Those who adopt this “if it ain’t broke, don’t fix it” position look upon refactoring as unnecessary meddlingwith something that already serves its purpose. Actually, this conformist posture that tries to maintain the“status quo” is the result of an intent to rationalize the fear of confronting the code and the fact that you donot have control over it.

Refactoring Is Nothing NewThis misconception could be restated as, “Refactoring is just another word for what we all know already.”Which means you have all learned about good code, object-oriented design, style, good practices, and soon, and refactoring is just another buzzword that someone invented to sell some books.

Okay, refactoring does not pretend to be imposing a radically new paradigm like object-oriented or aspect-oriented programming. What it does do is radically change the way you program: it defines rules thatmake it possible to apply complex transformations to code at the click of a button. You do not look at yourcode as some frozen construct that is not susceptible to change. Instead, you see yourself as capable ofmaintaining the code in optimum shape, responding efficiently to any new condition.

Refactoring Is Rocket ScienceProgramming is hard. It’s a complex activity that requires a lot of intellectual effort. Some of the knowl-edge can be very difficult to grasp. With Visual Basic .NET, VB programmers had to acquire the ability towork in a fully capable object-oriented language. For many, this was baffling at first. The good part is itdefinitely pays off.

The great thing about refactoring is how simple it can be. It equips you with a very small set of simplerules to start off. This, coupled with a good tool, makes first steps in refactoring a breeze. Compared toother techniques an advanced programmer should know nowadays, like UML or design patterns, I’dsay refactoring has the easiest learning curve, a lot like VB itself compared to other programming lan-guages. Very soon, the time spent in learning refactoring will start to reap rewards. Of course, as withany other thing in life, gaining mastery requires a lot of time and effort.

12

Part I: Introduction to Refactoring

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 12

Page 48: Professional Refactoring in Visual Basic

Refactoring Causes Poor PerformanceA longer way to state this might be, “Because after refactoring you usually end up with a larger num-ber of more fine-grained elements like methods and classes, so much indirection must incur some performance cost.”

If you go back in time a little, you’ll discover that this argument curiously sounds like the one used tovoice initial skepticism toward object-oriented programming. The truth is that the differences betweenrefactored and unstructured code are, at best, minimal. Except in some very specialized systems, this isnot a concern.

Experience shows that performance flows are generally afflicted by some precise spots in code. Fixingthose during an optimization phase will get you the required levels of performance. Being able to easilyidentify the critical pieces of code can prove to be very valuable. By producing understandable code inwhich duplication and total size is minimized, refactoring greatly aids this task.

Refactoring Breaks Good Object-Oriented DesignWell-structured and refactored code can look awkward to an untrained eye. Methods are so short that theyoften seem without substance. Classes seem without enough weight, consisting of only a few members. Itseems as if nothing ever happens in our code.

Having to manage a greater number of elements like classes and methods can imply that there is morecomplexity to deal with. This argument is actually misleading. The truth is that the same complexity wasalways present, only in refactored code it is expressed in such a cleaner, more structured way.

Refactoring Offers No Short-Term BenefitsRefactoring actually makes you program faster. So far, I do not know of any study that I could call uponin order to prove what I just said, but my own experience tells me this is the case. All the same, it is onlylogical that this is so. Because we have a smaller quantity of code overall, less duplication, and a clearerpicture, unless we are dealing with some trivial and unrealistically small scale code, benefits becomeapparent very soon.

Refactoring Works Only for Agile TeamsBecause it’s often mentioned as one of the pillar techniques in agile methodologies, refactoring is interpretedas working only for teams adhering to these principles.

Refactoring is indispensable for agile teams. Even if your team has a different methodology, most of thetime you are the one in charge charge of the way you code. Best results in refactoring are achieved if youadopt refactoring in small steps, performing it regularly while you code. Some practices, like strict codeownership or a waterfall process, can play against refactoring. If you can prove that refactoring makessense from a programming point of view, you can start building your support base, first with your peersand then by spreading the word to the rest of your team.

That dispenses with some of the common misconceptions surrounding refactoring. At this point, youmay be wondering how all of this relates to Visual Basic. That is the topic of the next section.

13

Chapter 1: Refactoring: What’s All the Fuss About?

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 13

Page 49: Professional Refactoring in Visual Basic

Visual Basic and RefactoringIt is fair to say that in the Visual Basic community, refactoring has had a slow start. One of the main rea-sons for this was the lack of proper tool support. While some tools with refactoring capabilities appearedon the market some years ago, only recently did dedicated refactoring tools for VB appear. Lack of toolscoupled with lack of information and scarce literature suited for VB developers led to slow adoption ofthe technique. It seems, however, that in this case the developer community was ahead of the industrypolicy makers and commercial institutions. Refactoring support was voted the number-one desired fea-ture for the 2005 edition of Visual Basic IDE. Realizing the importance this feature has for VB developers,Microsoft partnered with Developer Express to release a free Visual Basic refactoring add-in for VisualStudio 2005.

Visual Basic History and Legacy IssuesWhile refactoring was slow to take over in the Visual Basic community, it can be argued that refactoring has even greater importance for Visual Basic programmers than for programmers in some other languages.Visual Basic longevity means that VB developers need to deal with a host of legacy issues even today. Inthis effort, refactoring can be a great help by providing the programmer with the tools for unobtrusivetransformation of legacy constructs into more appropriate contemporary code.

Visual Basic has been in existence for more than 15 years. During that time it has earned a huge following —it has become one of the most popular programming environments in existence. Thanks to its syntax basedon the BASIC programming language and the graphical environment for drawing GUI elements, it hasproven to be an easily accessible programming environment with a gradual learning curve. And thanks toMicrosoft’s policy of spreading the use of Visual Basic in other forms and other environments like WindowsScripting, Visual Basic for Applications used for Office automation, Active Server Pages, and others, the circle of VB adopters has significantly increased.

Visual Basic Evolution Since its inception, Visual Basic has undergone a steady process of evolution and improvements. In version four, class constructs were added to Visual Basic, paving the way for object-oriented pro-gramming in Visual Basic. However, not until VB .NET did Visual Basic unleash the full power ofobject-oriented programming. In VB .NET, implementation inheritance support was added.

With the advent of the .NET Framework, Microsoft decided to make a more significant overhaul of thelanguage and to make it more appropriate for the new platform. Significant improvements were made:

❑ Added inheritance support

❑ Optional static type checking (Option Strict)

❑ Structured Error Handling (Try-Catch-Finally)

❑ Attributes

Many other programming elements were removed or replaced in an attempt to clean up the language syn-tax and make it more in the spirit of the .NET Framework. In the .NET Framework, all code gets compiledinto Intermediate Language and then traduced into native binary. This is true for code programmed in VB .NET also. This makes it possible for VB code to interact with code programmed in other languages.

14

Part I: Introduction to Refactoring

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 14

Page 50: Professional Refactoring in Visual Basic

VB programmers can use code programmed in C# or managed C++. The reverse is also true — C# or C++programmers, or programmers in any other .NET language for that matter, can import and use assembliesprogrammed in Visual Basic .NET. This is not so different from the interoperability provided by COM in thepre-.NET era, with the distinction that it is also possible to inherit types programmed in different languages.

This continuous work on language improvement continues even today. For example, in Visual Basic 2005,support for generic types was added. In the 2008 version of VB, new features like LINQ, XML Literals,Extension Methods, and Lambda Expressions continue to keep VB at the forefront of .NET programminglanguages.

Along with market forces that are continuously moving Visual Basic forward in making it more powerfuland advanced so that it stays as efficient as other programming languages, some frictional forces stand inthe way of its progress. Its long history and success are the main reasons for keeping some of its older lan-guage elements that would normally be completely replaced with newer ones. With C# Microsoft had aclean slate for language design. With Visual Basic, it has to take into account a huge amount of existingcode that should be migrated and a great number of developers who need to upgrade their skills.

Legacy CodeThe huge popularity and widespread adoption of Visual Basic resulted in a great amount of pre-.NETcode still in production even today, almost six years after the advent of .NET. Migrating VB6 or priorcode to .NET is not a simple affair. While Microsoft provided an automated migration tool, this upgradecan not be realistically performed without user interaction. Code produced by the Migration Wizard willoften leave portions of code that should be upgraded manually.

This in part demonstrates a somewhat brave decision by Microsoft not to subject VB .NET to upgradenecessities and backward-compatibility issues. But because a completely automated upgrade is notpossible, a number of features were kept in VB .NET in order to provide at least some possibility ofupgrade. Such features are:

❑ Old-style error handling (On error...)

❑ Optional static typing as opposed to mandatory static type checking (Option Strict...)

❑ Module language construct, etc.

Unfortunately, code with such features left over after the upgrade has not been fundamentally upgraded.It can execute in a new, .NET environment, but nevertheless it has kept old deficiencies.

Legacy ProgrammingIn the computer industry, new technologies are the order of the day. As programmers, you are destined to upgrade your skills continuously and to acquire new knowledge. Unless you do so, the spectrum ofemployment opportunities and chances for career advancement can be greatly reduced. VB programmerswere exposed to this process of continuous skill improvement throughout the history and evolution of VB,but never was the challenge as great as with the release of VB .NET.

By making VB a modern, fully object-oriented language with native access to the .NET Framework,Microsoft gave VB programmers a much more powerful tool. However, to be able to harness this power,programmers had to acquire new skills. It can be argued that with the advent of VB .NET a “paradigmshift” has been produced. The changes are significant and go beyond a simple upgrade. These changesrequire new skills and new ways of thinking.

15

Chapter 1: Refactoring: What’s All the Fuss About?

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 15

Page 51: Professional Refactoring in Visual Basic

However, because of many old elements that were kept in VB .NET, it is possible to program in VB .NETin the “old style,” just as in VB6 or prior. But if that way is used, no benefits are gained from the newplatform. This is not a new phenomenon in the history of programming. C++ and Java had their syntaxbased on C language syntax in order to attract and facilitate transfer of C programmers to these new lan-guages. However, it was soon noticed that a number of these programmers started using new tools andenvironments in the old style, relying on old constructs and design applicable to the old programminglanguage. With C++ and Java, this meant that programmers continued using procedural design insteadof relying on new, object-oriented design. Admittedly, in VB the gap is not that great. Even in VB6 youcan define a class, create an instance, or define and implement an interface. Nonetheless, using inheri-tance and generics and understanding the benefits of static typing are important challenges for someonecoming from a classic VB background, and surmounting these challenges requires a major shift in theway programming is approached.

Legacy Language ElementsSo these backward forces I’ve discussed led to a situation in which many of the obsolete language elementswere kept in VB .NET for the purpose of upgrades of existing code or with the intention of facilitating thetransition of programmers to a new platform.

Unfortunately, these features can just as easily be used in newly created VB .NET code, although thereare other, much better alternatives. To be able to distinguish between the two, a solid command of VB .NET and object-oriented principles is required.

Dealing with Legacy Issues Through RefactoringBuilding on the fundamentals of object-oriented theory, refactoring goes a step further than a traditionalapproach to programming does. With refactoring, it is easy to identify weak spots in your code and applysmall-scale transformations that do not change the behavior of code but can deal with the shortcomings. In that sense, some of the legacy language elements can be considered undesirable, and, just like any othersmell, this legacy code smell can be dealt with through the refactoring process. By defining procedures thatcan transform legacy elements and fundamentally upgrade the code, refactoring can greatly alleviate issuesrelated to upgrade and make your old code fully capable and equally useful in the .NET world.

SummaryThis introductory chapter has given you a brief overview of refactoring, explaining its relevance andbenefits. You have seen how refactoring helps you design your applications and prevent design rot, andat the same time accommodate any change that your software might be exposed to.

You have learned the three important stages in each cycle of the refactoring process: smell identification,refactoring, and testing. These three stages are mandatory for successful refactoring. In order to makethis process even more productive, you can rely on automated refactoring tools that take a lot of thedrudgery and complexity out of refactoring, making it easily accessible and applicable.

You have also also been presented with some of the most common misconceptions of refactoring thatyou might come across, just so you won’t be surprised by the diversity of opinions on the subject andcan make your own informed choices.

16

Part I: Introduction to Refactoring

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 16

Page 52: Professional Refactoring in Visual Basic

In the second part of the chapter, I put Visual Basic into focus. A very popular and successful tool, it wasnot immune to changes and advances in the programming world. While it has evolved significantly, itslongevity means that a host of legacy and backward-compatibility characteristics were preserved inside the language in an attempt to make the upgrade to new versions less upsetting. Unfortunately, this alsomeant that a lot of programmers continued to program in the legacy style, not reaping the benefits of theadvances of this fully object-oriented language that came with th advent of VB .NET.

You have seen how refactoring can play a significant role in the transition and upgrade of legacy code to VB .NET. You can rely on it while you acquire new knowledge and sharpen your programming anddesign skills.

Now it’s time to see some of this in practice. In the next chapter you are going to see refactoring at work.I will write some code and use a small application to illustrate the power of the refactoring process.

17

Chapter 1: Refactoring: What’s All the Fuss About?

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 17

Page 53: Professional Refactoring in Visual Basic

79796c01.qxd:WroxPro 2/25/08 8:55 AM Page 18

Page 54: Professional Refactoring in Visual Basic

A First Taste of Refactoring

Before I go into the details of refactoring procedures, the theory and mechanics behind it, it is agood idea to start with a very simple, yet complete application in order to gain perspective on theprocess I am trying to describe. This way, as I start going into detail about specific refactoring orcode smells, you’ll have a better sense of where each of these elements of the refactoring processfits and of the purpose behind it.

In this chapter you are going to see a simple application, consisting only of a single form and a single event-handling procedure, in its first incarnation. Soon, requirements will start to grow. Asthe application strives to respond to new requirements, flaws and imperfections in the design willmaterialize. I will identify the code smells and eliminate them as I progress. I’ll follow the refactor-ing process as I perform these modifications. As you have already seen, the refactoring processconsists of three steps:

1. Identifying the smell

2. Using specific refactoring to eliminate the smell

3. Executing tests to validate the changes

I’ll focus on the refactoring process, but I will explain and develop the complete application as I goalong. Of course, since all I am trying to do in this chapter is give you the first taste of refactoring,the application will be moderate in size and detail.

Calories Calculator Sample ApplicationThe application I am going to develop belongs to the medical field. It starts out as an application forcalculating one’s recommended daily intake of calories. In order to implement this first requirement, I wrote a single event-handling routine. However, as new requirements are added and the application

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 19

Page 55: Professional Refactoring in Visual Basic

grows, a series of refactorings will have to take place. In its final stage, the application consists of five classes,three of them organized as a part of a small inheritance hierarchy.

It is important to observe how complex solutions and refined structures appear only once the requirementsand code grow in quantity. This is in accord with the principle of promoting the simplicity of the solution asits most important characteristic. Almost all refactorings I perform will be initiated only once a code smell is detected. While I am eliminating the code smell, I’ll show you that some underlying abstractions appearrelated to the domain I am working on: Patient, Male Patient, Female Patient, etc. You’ll see how work onthe elimination of code smells and work on the design based on object-oriented principles converge into thecreation of an efficient and robust solution.

More complex, large-scale systems might benefit from a more elaborately designed solution right fromthe outset. In this case the application is so simple that any complex design up front would probablyindicate that I have overengineered the solution.

Calories Calculator ApplicationMy client, a doctor, needed an application to calculate a recommended daily amount of calories. He hadthe formula and could perform the calculation manually, but using a calculator is too tedious and error-prone. The calculation is based on some of the patient’s personal data and differs depending on gender.Here are the formulas:

❑ Male: 66 + (6.3 × body weight in lbs.) + (12.9 × height in inches) – (6.8 × age in years)

❑ Female: 655 + (4.3 × weight in lbs.) + (4.7 × height in inches) – (4.7 × age in years)

The formulas seem pretty straightforward. I opened VB and designed the Calories Calculator formshown in Figure 2-1.

Figure 2-1

This single form contains patient data input controls and displays the recommended daily amount ofcalories in the Recommended Daily Amount read-only text box.

The code to calculate the recommended daily amount was not that complicated either. All that wasrequired was a click on the Calculate button and the following event-handling code that I wrote(Listing 2-1).

20

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 20

Page 56: Professional Refactoring in Visual Basic

Listing 2-1: The BtnCalculate_Click Code

Private Sub BtnCalculate_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnCalculate.Click

If (RbtnMale.Checked) ThenTxtCalories.Text = 66 + (6.3 * TxtWeight.Text) + _(12.9 * (TxtFeet.Text * 12 + TxtInches.Text)) - _(6.8 * TxtAge.Text)

ElseTxtCalories.Text = 655 + (4.3 * TxtWeight.Text) + _(4.7 * (TxtFeet.Text * 12 + TxtInches.Text)) - _(4.7 * TxtAge.Text) End If

End Sub

I experimented with the form for a while, and it proved to work reasonably well. However, I noticed Iwould need to add some user input-verification routines, or the program might display some unpleas-ant error messages and exit. Listing 2-2 shows the code for my first version of the application.

Listing 2-2: Calories Calculator First Try

Private Sub BtnCalculate_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnCalculate.Click

‘Validate user input:‘Validate height (feet) is numeric value If Not IsNumeric(TxtFeet.Text) Then

MsgBox(“Feet must be a numeric value!”)TxtFeet.Select()Return

End If‘Validate height (inches) is numeric valueIf Not IsNumeric(TxtInches.Text) Then

MsgBox(“Inches must be a numeric value!”)TxtInches.Select()Return

End If ‘Validating weight is numeric value If Not IsNumeric(TxtWeight.Text) Then

MsgBox(“Weight must be a numeric value greater then zero!”)TxtWeight.Select()Return

End If‘Validate age is numeric valueIf Not IsNumeric(TxtAge.Text) Then

MsgBox(“Age must be a numeric value greater then zero!”)TxtAge.Select()Return

End If‘calculate amount of calories If (RbtnMale.Checked) Then

‘Apply one formula for male patientTxtCalories.Text = 66 + (6.3 * TxtWeight.Text) + _(12.9 * (TxtFeet.Text * 12 + TxtInches.Text)) - _(6.8 * TxtAge.Text)

Continued

21

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 21

Page 57: Professional Refactoring in Visual Basic

Listing 2-2: Calories Calculator First Try (continued)

Else ‘Apply another formula for female patientTxtCalories.Text = 655 + (4.3 * TxtWeight.Text) + _(4.7 * (TxtFeet.Text * 12 + TxtInches.Text)) - _(4.7 * TxtAge.Text)

End IfEnd Sub

I have used the IsNumeric function in order to validate that values inputted by the user are numeric. Ifthe user is inputting some invalid characters, a message box will appear informing him of the mistake,and the method will exit.

So far the application is so simple that it does not deserve more effort. But you can already see that thereis a lot of code mixed up in the single method, and that if the application starts to grow, I will probablyneed to restructure it in order to make it more atomic.

At this point I thought that the job was done, and I sent the executable to my client.

Growing Requirements: Calculating Ideal WeightNot to my surprise, some time after submitting the application, I received an e-mail from my client ask-ing me to add more features to the Calories Calculator. The required feature is ideal-weight calculation.The application should calculate the ideal weight for the patient based on height. Again, calculationdepends on gender and there are separate formulas for men and women.

Here are the formulas:

❑ Male: 50 + 2.3 kg per inch over 5 feet

❑ Female: 45.5 + 2.3 kg per inch over 5 feet

I decided to implement the requested functionality by modifying the existing form. I added a few con-trols, as shown in Figure 2-2.

Figure 2-2

22

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 22

Page 58: Professional Refactoring in Visual Basic

As I programmed the formula for calculating ideal weight, I made the following changes:

❑ I noticed that, as written, it applies only to people standing at or above five feet tall. In order to take this limitation into account, I added the verification code for the patient’s height. If theentered height value is less then five feet, a message box with information on the height limitwill be displayed, and the method will exit.

❑ I also noticed that I would need to clear old results before each calculation, because otherwisethey might confuse the user. I added a few lines of code that that clean text boxes each time theCalculate button is pressed. This way, even when invalid data are entered, results will be clearedbefore the user is informed of erroneous input.

Listing 2-3 shows how I implemented the new feature.

Listing 2-3: Calories Calculator with Ideal-Body-Weight Calculation

Private Sub BtnCalculate_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnCalculate.Click

‘Clear old resultsTxtCalories.Text = “” TxtIdealWeight.Text = “” TxtDistance.Text = “” ‘Validate user inputIf Not IsNumeric(TxtFeet.Text) Then

MsgBox(“Feet must be a numeric value!”)TxtFeet.Select()Return

End IfIf Not IsNumeric(TxtInches.Text) Then

MsgBox(“Inches must be a numeric value!”)TxtInches.Select()Return

End IfIf Not IsNumeric(TxtWeight.Text) Then

MsgBox( _“Weight must be a numeric value greater then zero!”)TxtWeight.Select()Return

End IfIf Not IsNumeric(TxtAge.Text) Then

MsgBox( _“Age must be a numeric value greater then zero!”)TxtAge.Select()Return

End If‘Ideal body weight works only if taller than 5 ft If Not (TxtFeet.Text >= 5) Then

MsgBox(“Height has to be equals or greater than 5 feet!”) TxtFeet.Select()

End If ‘calculate amount of calories and ideal body weightIf (RbtnMale.Checked) Then

‘Calculates ideal weight for men according to formulaContinued

23

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 23

Page 59: Professional Refactoring in Visual Basic

Listing 2-3: Calories Calculator with Ideal-Body-Weight Calculation (continued)

TxtCalories.Text = 66 + (6.3 * TxtWeight.Text) + _(12.9 * (TxtFeet.Text * 12 + TxtInches.Text)) - _(6.8 * TxtAge.Text) ‘calculate ideal body weight TxtIdealWeight.Text = (50 + (2.3 * (((TxtFeet.Text - 5) _ * 12) + TxtInches.Text))) * 2.2046

Else‘ Calculates ideal weight for women according to formula TxtCalories.Text = 655 + (4.3 * TxtWeight.Text) + _(4.7 * (TxtFeet.Text * 12 + TxtInches.Text)) - _(4.7 * TxtAge.Text)‘calculate ideal body weight TxtIdealWeight.Text = (45.5 + (2.3 * (((TxtFeet.Text - 5) _ * 12) + TxtInches.Text))) * 2.2046

End If‘Calculate and display distance from ideal weight TxtDistance.Text = TxtWeight.Text - TxtIdealWeight.Text

End Sub

That was not so difficult. Actually, I just went into the old code and added new code to fulfill the newrequirement of calculating ideal weight.

❑ When applying the formula, I subtracted five feet from the feet value and then converted theresulting height into inches: (((TxtFeet.Text - 5) * 12) + TxtInches.Text))).

❑ Then I applied the formula by multiplying the height in inches by 2.3 and adding to that result50 in the case of a male patient and 45.5 in the case of a female patient.

❑ Since the result obtained this way represents the value in kilograms, I had to multiply it by2.2046 in order to convert it to pounds.

However, now you may notice that the sole method in my application (BtnCalculate_Click) hasbecome alarmingly long, and the ideal-body-weight calculation is not easy to understand.

Adding the necessary code to existing classes and methods is a very typical way to add new require-ments to existing applications. This can easily lead to poorly structured and tangled code. While eventhis short sample could benefit from refactoring at this point, I used my own judgment, based on com-mercial circumstances, and decided not to spend any more time on improving the application unless I was asked to implement more functionality.

Again, I sent the application to my client. Somehow, I had a feeling that this would not be the end ofthe story!

Growing Requirements: Persisting Patient DataThe next requirement the client asked for confirmed the fact that the application has demonstrated its value and is really being used by the client. The client expressed a need to somehow save patients’historical data so that he could easily track the patients’ progress.

I want to stop a moment and analyze this latest requirement. If you take a look at the existing form, you’llnotice that it provides no means of identifying the patient for whom the calculation is being performed. So

24

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 24

Page 60: Professional Refactoring in Visual Basic

for this requirement I would need to capture some data to use to identify the patient. I also need to persistthe data and to display it.

In conclusion, the Calories Calculator application now needs to provide the following functionality:

❑ Identifying the patient. The first step is getting some data that will be used to identify thepatient. I decided to use patient names and Social Security numbers (SSNs). Names make iteasy for the doctor to identify the patient, and the SSN prevents any possible mix-up in casepatients have the same names.

❑ Keeping the calculation operation and the operation of persisting patient data separate. At firstI was tempted to add some code to the existing BtnCalculate_Click event-handling routineand to use it to save patient data. But looking at it from the users’ perspective, I decided thatthese two actions should be separated. This way, the users will be able to perform some ad hoccalculations without having to input a patient’s name and SSN. Also, the users can save data onlywhen they are sure that all entries are correct.

❑ Displaying a patient’s history. Users will need some easy way to display the patient’s history.

All of this functionality requires additional changes to the form. I added a few controls (for patient dataand history capability), and the form ended up looking like the one in Figure 2-3.

Figure 2-3

Once I made the changes, the form worked as follows:

❑ The Patient personal data section added data necessary to identify and differentiate patients.

❑ The Save and View buttons were added so the users can persist patient data or display patient history.

❑ The Calculate button maintained its function.

25

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 25

Page 61: Professional Refactoring in Visual Basic

There are a few alternatives for implementing persistence of a patient’s history. You could use a flat file,a database like Access, or even a spreadsheet. The simplest solution, however, is probably to use an XMLfile, so I decided to do that. But before I dedicate myself to implementing new requirements, the timehas come to look at the code produced so far and to deal with any outstanding issues.

I have been postponing this so far and have simply added more code to the existing method, since Iwasn’t sure I was going to continue to work on the application. However, now that the application hasgrown and more complex requirements are to be implemented, I need to restructure the existing code.The first and very obvious problem is that I have a single method implementing the whole functionalityof the application. This method has grown out of proportion. Long methods are difficult to understandand reuse.

I’ll return to the issue of persisting patient data later in the chapter, but I want to deal first with the out-standing issues that can stand in the way of implementing the new functionality, and that requires somerefactoring. You can see that I haven’t really gone that far with implementation, but some smells havealready managed to creep inside the code. I will deal with these smells before I go any further.

Refactoring in ActionSo far I haven’t been paying too much attention to how I programmed the Calories Calculator application.It was rather simple, and this straightforward approach worked well. With new requirements, things canget more complicated; bearing in mind the interest the application created, it is an auspicious moment togive it some more thought.

If I continue to simply add new functionality, the application will soon grow out of control and becomeimpossible to maintain. I am going to reorganize the code so its internal structure is improved. I’ll do itwithout changing its behavior; the calorie calculation will happen just as it used to. If you remember thedefinition of refactoring (discussed thoroughly in Chapter 1), you’ll see that this is exactly what I plan todo with the Calories Calculator.

In this section, I will start off by looking into the code created so far. The only routine, BtnCalculate_Click, looks alarmingly long. I used comments in order to separate it into several part; even whencommented, long methods are much more difficult to understand. In refactoring terminology, thismethod displays a “long method smell.” The solution is to split it into several shorter methods.

Unit Testing: Since I am now starting to change the code in ways that can haveadverse effects and even introduce bugs, I need to make sure that application consis-tency is maintained at all times. I can do this by developing a set of tests. I will writedown a few imaginary patients’ data and the results of the calculations the applica-tion is performing. Then I will run the application each time I make a change to itand execute all those tests, making sure correct results are returned. I will also testfor invalid data. This sounds tedious, but it’s necessary. Fortunately, there is a muchbetter way to do this — as you’ll see in Chapter 3, in which I discuss unit testing.

26

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 26

Page 62: Professional Refactoring in Visual Basic

Decomposing the BtnCalculate_Click MethodOne very natural way of dealing with a long method is to separate it into multiple shorter methods.Doing this with BtnCalculate_Click is really rather obvious. If you take a look at Listing 2-3, you’llsee that the code comments essentially segment the code into modules that indicate to you where youshould extract the code into the new methods. This will not change the behavior of the code, but it willmake the code better organized and make the BtnCalculate_Click method easier to read. I will movethe code into the new method and make BtnCalculate_Click call the new method. The next sectionsshow how I take each segment from the method and make that segment its own method.

The “Clear Old Results” SegmentThis segment is transformed into ClearResults private method:

Private Sub ClearResults()TxtCalories.Text = “”TxtIdealWeight.Text = “”TxtDistance.Text = “”

End Sub

The “Validate User Input” Segment This segment is transformed into the ValidateUserInput private method:

Private Function ValidateUserInput() As BooleanIf Not IsNumeric(TxtFeet.Text) Then

MsgBox(“Feet must be a numeric value!”)TxtFeet.Select()Return False

End IfIf Not IsNumeric(TxtInches.Text) Then

MsgBox(“Inches must be a numeric value!”)TxtInches.Select()Return False

End IfIf Not IsNumeric(TxtWeight.Text) Then

MsgBox( _“Weight must be a numeric value greater then zero!”)TxtWeight.Select()Return False

End IfIf Not IsNumeric(TxtAge.Text) Then

MsgBox( _“Age must be a numeric value greater then zero!”)TxtAge.Select()Return False

End If‘Ideal body weight works only if taller than 5 ftIf Not (TxtFeet.Text >= 5) Then

MsgBox(“Height has to be equals or greater than 5 feet!”)TxtFeet.Select()Return False

End IfReturn True

End Function

27

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 27

Page 63: Professional Refactoring in Visual Basic

This method needs to return the result of user input verification. In case the data are not valid, I need tointerrupt the execution of the BtnCalculate_Click method. You will see that approach when you takea look at the refactored BtnCalculate_Click method.

The “Calculate and Display Distance from Ideal Weight” Segment This segment can be transformed into the DistanceFromIdealWeight private method, so it receivestwo parameters, actual weight and ideal weight:

Public Function DistanceFromIdealWeight( _ByVal actualWeightInPounds As Decimal, _ByVal idealWeightInPounds As Decimal) As Decimal

Return actualWeightInPounds - idealWeightInPoundsEnd Function

Notice that in order to avoid any confusion in regards to measurements used, I added the name of themeasurement to the parameter names — actualWeightInPounds and idealWeightInPounds.

Calculating Calories and Ideal Weight by GenderThe last remaining section is a little less self-explanatory than the others. The application will calculatecalories or ideal weight, but it does so according to gender. So the easiest way to approach this issue is toturn the section into four private methods:

Public Function DailyCaloriesRecommendedMan( _‘Parameter name includes measurement type info: pound, inchByVal weightInPounds As Decimal, _ ByVal heightInInches As Decimal, _ ByVal age As Integer) As Decimal

Return 66 + (6.3 * weightInPounds) + _(12.9 * heightInInches) - (6.8 * age)

End Function

Public Function DailyCaloriesRecommendedWoman( _ByVal weightInPounds As Decimal, _ ByVal heightInInches As Decimal, _ ByVal age As Integer) As Decimal

Return 655 + (4.3 * weightInPounds) + _(4.7 * heightInInches) - (4.7 * age)

End Function

Public Function IdealBodyWeightMan( _ByVal heightInInches As Decimal) As Decimal

Return (50 + (2.3 * (heightInInches - 60))) * 2.2046End Function

Public Function IdealBodyWeightWoman( _ ByVal heightInInches As Decimal) As Decimal

Return (45.5 + (2.3 * (heightInInches - 60))) * 2.2046End Function

Instead of using code comments to indicate the intention behind a few lines of code, I now use a methodto achieve the same goal. Method names clearly describe their purpose. This way the code is a lot easier tounderstand and reuse. As I move code to new methods, I eliminate the comments.

28

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 28

Page 64: Professional Refactoring in Visual Basic

The technique of decomposing large methods by extracting pieces of method into the new methods iscalled extract method refactoring. I discuss this refactoring in detail in Chapter 9 and Chapter 10.

The BtnCalculate_Click Method after Method ExtractionWhat does the BtnCalculate_Click routine look like now? You can see this in Listing 2-4.

Listing 2-4: The BtnCalculate_Click Method after the Decomposition

Private Sub BtnCalculate_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnCalculate.Click

ClearResults()If ValidateUserInput() = False Then ReturnIf (RbtnMale.Checked) Then

TxtCalories.Text = DailyCaloriesRecommendedMan( _TxtWeight.Text, TxtFeet.Text * 12 + TxtInches.Text, _TxtAge.Text)TxtIdealWeight.Text = IdealBodyWeightMan(TxtAge.Text)

ElseTxtCalories.Text = DailyCaloriesRecommendedWoman( _TxtWeight.Text, TxtFeet.Text * 12 + TxtInches.Text, _TxtAge.Text)TxtIdealWeight.Text = IdealBodyWeightWoman(TxtAge.Text)

End IfTxtDistance.Text = DistanceFromIdealWeight( _TxtWeight.Text, TxtIdealWeight.Text)

End Sub

It’s definitely a lot shorter method now. Also, the comments are gone, since they are of no use any more.New method names are equally expressive.

The result of this transformation is multiple, shorter methods that do exactly what the original singlemethod did. The benefits of more granular code are improved clarity and greater possibility for reuse of new shorter methods.

Discovering New ClassesIn object-oriented programming you often use classes to establish links in code with real-world phenomena.This mapping between elements in the code and domain entities can greatly improve readability and com-prehension of code. So far, I didn’t even attempt to model the application code according to object-orientedprinciples. Instead, I have a single class inheriting System.Windows.Forms.Form, a base class for a basicGUI element. This class now sports a few methods I created by decomposing the event-handling method.

If you analyze for the moment the code created so far, you can see that the newly created methods can bedivided into two distinct groups:

❑ Methods that are related to the behavior of the visual elements, the GUI, like the ClearResultsmethod

❑ Methods that embed the medical formulas that perform the calculations, likeIdealBodyWeightMan and DailyCaloriesRecommendedWoman

29

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 29

Page 65: Professional Refactoring in Visual Basic

So far the FrmCaloriesCalculator class is the only one in the project. It extends the Form class, abase class for windows or dialog boxes, which is a fundamental building block for the desktop inter-face. This class works well for our first group of methods, but the second group of methods, concernedwith medical calculations, clearly does not belong to it.

By running the Calories Calculator application, you can observe that its purpose is collecting and com-puting certain data. This data represents human characteristics. In this case, as you can observe on theform itself, this data refers to patients.

Defining the Patient ClassSince this application deals with a medical problem, it makes sense to structure the data inside a newclass called Patient. Listing 2-5 shows the class definition.

Listing 2-5: The New Patient Data Class

Public Class PatientPrivate ssnValue As StringPrivate firstNameValue As StringPrivate lastNameValue As StringPrivate heightInInchesValue As DecimalPrivate weightInPoundsValue As DecimalPrivate ageValue As IntegerPublic Property SSN() As String

GetReturn ssnValue

End GetSet(ByVal Value As String)

ssnValue = ValueEnd Set

End PropertyPublic Property FirstName() As String

GetReturn firstNameValue

End GetSet(ByVal Value As String)

firstNameValue = ValueEnd Set

End PropertyPublic Property LastName() As String

GetReturn lastNameValue

End GetSet(ByVal Value As String)

lastNameValue = ValueEnd Set

End PropertyPublic Property HeightInInches() As Decimal

GetReturn heightInInchesValue

End GetSet(ByVal Value As Decimal)

heightInInchesValue = Value

30

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 30

Page 66: Professional Refactoring in Visual Basic

Listing 2-5: The New Patient Data Class (continued)

End SetEnd PropertyPublic Property WeightInPounds() As Decimal

GetReturn weightInPoundsValue

End GetSet(ByVal Value As Decimal)

weightInPoundsValue = ValueEnd Set

End PropertyPublic Property Age() As Integer

GetReturn ageValue

End GetSet(ByVal Value As Integer)

ageValue = ValueEnd Set

End PropertyEnd Class

So far this class is a simple data structure, consisting only of properties. While grouping the data inside anew data type is definitely a step forward in attempting to improve the design of the application, havinga class that has only data and no behavior is another hint that I need to continue with improvements onthe design of the program. A class with only data and no behavior is referred to as a data class and is con-sidered a code smell.

Moving Methods to the Patient ClassIf you now go back to the second group of methods, the ones performing medical computations, you cansee they operate on exactly the same patient data that the Patient class is used to describe.

So, according to some basic object-oriented principles, I should keep operations close to the data theyrefer to. I can do the following:

1. Move the DailyCaloriesRecommendedMan, DailyCaloriesRecommendedWoman,IdealBodyWeightMan, and IdealBodyWeightWoman methods to the Patient class.

2. Because, methods have access to private variables of the class, I can make these four methods use Patient class properties instead of receiving parameters, and eliminate parameters from the method declaration because the body of the method refers to the same names I have given to properties in the Patient class. Visual Studio takes care of adjusting the case of letters. Just toillustrate it:

Public Function DailyCaloriesRecommendedWoman( _ByVal weightInPounds As Decimal, _ByVal heightInInches As Decimal, _ByVal age As Integer) As Decimal

Return 655 + (4.3 * weightInPounds) + _(4.7 * heightInInches) - (4.7 * age)

End Function

31

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 31

Page 67: Professional Refactoring in Visual Basic

is transformed into:

Public Function DailyCaloriesRecommendedWoman() As DecimalReturn 655 + (4.3 * WeightInPounds) + _(4.7 * HeightInInches) - (4.7 * Age)

End Function

Weight, Height, and Age all now refer to properties of the Patient class. In this case, parameter andproperty names coincided by chance. Normally this would require changing the body of the methods sothat they refer to properties and not method parameters. You can see how this works out in a more graphicway in Figure 2-4, where the DailyCaloriesRecommendedWoman method references the Height propertyin the same Patient class.

Figure 2-4

The Patient class is now a fully fledged class consisting of properties and methods, grouping the dataand related behavior inside a cohesive whole. I discuss Extract class and Move Method refactoring indetail in Chapter 11.

Narrowing the Patient Class InterfaceIf you have been paying attention so far, then you might have noticed that I didn’t treat a very impor-tant piece of data related to a patient in any special way. I am referring to gender. Unless I have thisinformation available inside the Patient class along with the rest of the patient data, I will not be able to move the last function left, DistanceFromIdealWeight, to make it a method in the Patientclass. The method needs to know both the actual and the ideal body weight in order to perform the calculation. But after the latest refactorings, I have two methods that can give me ideal bodyweight: IdealBodyWeightMan and IdealBodyWeightWoman. So which one of the two should theDistanceFromIdealWeight method call?

Public Class Patient Private heightInInchesValue As Decimal '... Public Property HeightInInches() As Decimal Get Return heightInInchesValue End Get Set(ByVal Value As Decimal) heightInInchesValue = Value End Set End Property '... Public Function DailyCaloriesRecommendedWoman() _ As Decimal Return 655 + (4.3 * WeightInPounds) + _ (4.7 * HeightInInches) - (4.7 * Age) End Function '...End Class

Parameters eliminated

HeightInInches references Patient class property

32

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 32

Page 68: Professional Refactoring in Visual Basic

Creating the Gender EnumI’ll set up the Patient class with another property, Gender. In this case, gender can have one of two values:male or female. To express this, I’ll be better off if I add another element to the project — the enumeratorGender. This way, DistanceFromIdealWeight can check the patient’s gender and call the correct calcula-tion method for ideal body weight.

Public Enum GenderMaleFemale

End Enum

Adding a Gender Property to the Patient ClassIt is quite easy now to add a Gender property to the Patient class that is typed as Enum Gender. It willhelp discern a gender for each patient instance. Do what you usually do when you add a new propertyto a class:

1. Add another private variable to keep the value of the Gender property:

Private genderValue As Gender

2. Add a typical body of the property Gender:

Public Property Gender() As GenderGet

Return genderValueEnd GetSet(ByVal Value As Gender)

genderValue = ValueEnd Set

End Property

Moving DistanceFromIdealWeight to Patient ClassOnce the Gender enumerator is created and the Gender property is added, I can move theDistanceFromIdealWeight method to the Patient class. Again, I eliminate parameters and add a simple condition that uses the Gender property, so it works correctly:

Public Function DistanceFromIdealWeight() As DecimalIf Gender.Male Then

Return WeightInPounds - IdealBodyWeightMan()Else

Return WeightInPounds - IdealBodyWeightWoman()End If

End Function

The method now works fine.

33

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 33

Page 69: Professional Refactoring in Visual Basic

Now, before I modify FrmCaloriesCalculator so it uses the Patient class, I want take another look atthe methods in the new class. I suspect there’s more refactoring that can be performed here before I moveon. Since I now have gender data available to code in the class, I wonder if having different methods for themale and female patients is really necessary. Maybe I could move the gender logic inside the class some-how and release the client from the burden of tracking the gender information? Shouldn’t the Gender classbe able to manage gender-related logic on its own?

Putting Conditional Logic in the Patient ClassA lot of method names in the Patient class, like DailyCaloriesRecommendedMan andIdealBodyWeightWoman, terminate with the word Man or Woman, indicating that the method is applicable to patients of a certain gender only. This will result in clients (and here when I say client, I mean anyone using the class) having to think about this condition and having to write code like this:

If myPatient.Gender = Gender.Male ThenidealWeight = myPatient. IdealBodyWeightMan()

ElseidealWeight = myPatient. IdealBodyWeightWoman()

End If

If you compare this to the DistanceFromIdealWeight method, you can see quite a different approachthere. The method itself contains this logic, because gender data are already available in the Patientclass. There are two obvious benefits to moving this conditional logic into the Patient class itself, aswith the DistanceFromIdealWeight method:

❑ There are fewer publicly visible methods in the Patient class, so things are a lot simpler foranyone using this class.

❑ Because classes are generally referenced from more than one place, moving conditional codeinside the method prevents a lot of duplication in the client code. No doubt anywhere thePatient class is used, the code would contain a lot of Ifs and end up looking like this:

If myPatient.Gender = Gender.Male Then‘... male patient related codeElse‘... female patient related codeEnd If

In short, I’m going to be better off if I transform all the methods along the same lines asDistanceFromIdealWeight. So I’ll need to add a few new methods to the Patient class. The next couple of subsections takes a look at how I do that.

At this point, you can see that I have succeeded in moving all the relevant logic to the Patient class. This is a great feat, since the potential reusability of the code hasimproved greatly. You can now easily imagine the Patient class being used in anothermodule someone might develop, or even in some other, future application. This wasnot at all the case with a sole FrmCaloriesCalculator class.

34

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 34

Page 70: Professional Refactoring in Visual Basic

Encapsulating DailyCaloriesRecommendedMan andDailyCaloriesRecommendedWoman

First I’ll make the methods DailyCaloriesRecommendedMan and DailyCaloriesRecommendedWomanvisible through a single method, DailyCaloriesRecommended:

Public Function DailyCaloriesRecommended() As DecimalIf Gender.Male Then

Return DailyCaloriesRecommendedMan()Else

Return DailyCaloriesRecommendedWoman()End If

End Function

Encapsulating Ideal-Body-Weight CalculationThen the same fate awaits the ideal-body-weight methods. Methods IdealBodyWeightMan andIdealBodyWeightWoman are now visible through a single method, IdealBodyWeight:

Public Function IdealBodyWeight() As DecimalIf Gender.Male Then

Return IdealBodyWeightMan()Else

Return IdealBodyWeightWoman()End If

End Function

Making Encapsulated Methods PrivateFinally, I can’t forget to reduce the visibility of all the SomeFunctionMan and SomeFunctionWomanmethods to private. The declarations now look like this:

Private Function DailyCaloriesRecommendedMan() As DecimalPrivate Function DailyCaloriesRecommendedWoman() As DecimalPrivate Function IdealBodyWeightMan() As DecimalPrivate Function IdealBodyWeightWoman() As Decimal

With those declarations in place, I have managed to further encapsulate the logic inside the Patientclass. This way no unnecessary logic is exposed to the Patient class client. As you know, encapsulationis one of the pillars of object orientation, and taking this step will make your code more robust.

Restructuring the DistanceFromIdealWeight MethodI still have one more method to deal with. The DistanceFromIdealWeight code can be written in a moreexpressive manner, so it is easier to read and understand. While containing the same condition, and in thatsense being similar to other methods, DistanceFromIdealWeight does not yet use private methods. I willadd two private methods, DistanceFromIdealWeightMan and DistanceFromIdealWeightWoman, struc-turing them just like the methods DailyCaloriesRecommended and IdealBodyWeight:

Private Function DistanceFromIdealWeightMan() As DecimalReturn WeightInPounds - IdealBodyWeightMan()

End Function

35

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 35

Page 71: Professional Refactoring in Visual Basic

Private Function DistanceFromIdealWeightWoman() As DecimalReturn WeightInPounds - IdealBodyWeightWoman()

End Function

DistanceFromIdealWeight now looks like this:

Public Function DistanceFromIdealWeight() As DecimalIf Gender.Male Then

Return DistanceFromIdealWeightMan()Else

Return DistanceFromIdealWeightWoman()End If

End Function

Again, I performed method extraction. This way, the method is structured along the same lines as othermethods, and you will see the benefits of this modification later in the chapter, when I create the Patientclass hierarchy.

In short, I have simplified the class for the client. In fact, you could say that class interface has been reduced,thereby simplifying the use of the classes. (Here by interface I refer to all public elements in the class.)

Making FrmCaloriesCalculator Use the Patient ClassNow I can modify the FrmCaloriesCalculator and make it use the Patient class for all related calcu-lations. I’ll add a Patient property to the class. The code looks like this:

Public Class FrmCaloriesCalculator‘ ...Private patientValue As Patient

Private Property Patient() As PatientGet

Return patientValueEnd GetSet(ByVal Value As Patient)

patientValue = ValueEnd Set

End Property‘ ...

This instance is created in the BtnCalculate_Click event-handling routine. I’ll add the following lineto it:

Me.Patient = New Patient

Now I need to set Patient’s properties:

If (RbtnMale.Checked) ThenMe.Patient.Gender = Gender.Male

ElseMe.Patient.Gender = Gender.Female

End IfPatient.HeightInInches = TxtHeight.Text

36

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 36

Page 72: Professional Refactoring in Visual Basic

Patient.Weight() = TxtWeight.TextPatient.Age = TxtAge.Text

Next, the Patient instance is used to perform the required calculations:

TxtCalories.Text = Patient.DailyCaloriesRecommendedTxtIdealWeight.Text = Patient.IdealBodyWeightTxtDistance.Text = Patient.DistanceFromIdealWeight

Finally, to see how BtnCalculate_Click ends up, take a look at Listing 2-6.

Listing 2-6: The BtnCalculate_Click Method After the New Patient Class Has Been Identified

Private Sub BtnCalculate_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnCalculate.Click

ClearResults()If ValidateUserInput() = False Then Return‘Creating new instance of Patient classMe.Patient = New Patient‘Setting patient properties with data from formIf (RbtnMale.Checked) Then

Me.Patient.Gender = Gender.MaleElse

Me.Patient.Gender = Gender.FemaleEnd IfPatient.HeightInInches = _(TxtFeet.Text * 12) + TxtInches.TextPatient.WeightInPounds() = TxtWeight.TextPatient.Age = TxtAge.Text‘Outputting calculated values to formTxtCalories.Text = Patient.DailyCaloriesRecommendedTxtIdealWeight.Text = Patient.IdealBodyWeightTxtDistance.Text = Patient.DistanceFromIdealWeight

End Sub

By extracting the Patient class and moving the logic related to medical calculation to this newly createdclass, I have laid the foundations of object-oriented design in the application. I have moved from an exclu-sively event-driven paradigm to an object-oriented paradigm. While solely event-driven programming canbe effective on the small scale, it has to be coupled with object-oriented design to be as effective for morecomplex applications.

Creating the Patient Class HierarchyNow that I have made all the preceding important steps in redesigning the application, it is time to takeanother look at the code I ended up with. After the latest changes, a certain uniformity in all of the methodsis becoming apparent. They all contain the same conditional code:

If Gender.Male Then‘Some code

Else‘Some code

End If

37

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 37

Page 73: Professional Refactoring in Visual Basic

In effect, this code — condition tests for a very specific attribute of Patient, the gender — has beenduplicated. I might as well have used copy-paste to create it. This is a very good indication that somefurther refinements are required. I have just identified another major code smell. Code duplication gener-ally indicates a serious flaw in your design: you wrote the same code more than once, meaning that ifyou need to modify this logic, you will need to find all occurrences of it in your code. It is all too easy tomiss other occurrences when the section that is of interest at that moment is modified. It also means thatyou have more code than necessary to worry about.

There is a way to go about fixing this problem. In this case I’ll create two new classes: MalePatient andFemalePatient. They will inherit the Patient class. Then I’ll move all gender-specific code to eitherMalePatient or FemalePatient, as appropriate. (You can take a look at Chapter 12, where I deal withExtract Super Class and other inheritance-related refactorings.)

The mechanics for this transformation are a bit more complicated. They are as follows:

1. First, I need to add two new classes that inherit the Patient class:

Public Class MalePatientInherits Patient

and

Public Class FemalePatientInherits Patient

2. The next step involves moving private methods with names ending with Male to the MalePatientclass, and private methods with names ending with Female to the FemalePatient class. This is anexample of pull-down method refactoring. Pulling down refers to moving a method from a class inthe hierarchy to another class lower in the inheritance hierarchy.

3. Next the Patient class declares methods that will have their implementation in its subclasses,MalePatient and FemalePatient. Since only some of the Patient class members are imple-mented, this class is abstract and must be marked by the MustInherit keyword:

Public MustInherit Class Patient

4. Methods in the Patient class that are implemented in MalePatient and FemalePatientneed to be marked as abstract by means of the MustOverride keyword. Once these methodsare marked as abstract, their bodies have to be removed. DistanceFromIdealWeight,DailyCaloriesRecommended, and IdealBodyWeight in the Patient class now look like this:

Public MustOverride Function DistanceFromIdealWeight() As Decimal

Public MustOverride Function DailyCaloriesRecommended() As Decimal

Public MustOverride Function IdealBodyWeight() As Decimal

Now I have the two new classes I need, MalePatient and FemalePatient, each containing the codespecific to the appropriate gender.

38

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 38

Page 74: Professional Refactoring in Visual Basic

Dealing with Gender-Specific MethodsNow, in the MalePatient and FemalePatient subclasses, I still have methods — methods that provideimplementation for abstract methods in the Patient class — ending with the words Man and Woman. It istime to change this. I will eliminate Man from method names in the MalePatient class and Woman frommethod names in the FemalePatient class. This way, the names of the methods in MalePatient andFemalePatient will coincide with method names in the Patient class. I’ll also change the visibility ofthe methods to public. Also, at this point I am getting a timely reminder from our IDE, visible in the TaskList window:

“Class ‘FemalePatient’ must either be declared ‘MustInherit’ or override the following inherited‘MustOverride’ member(s): Public MustOverride Function DistanceFromIdealWeight() As Decimal,Public MustOverride Function DailyCaloriesRecommended() As Decimal, Public MustOverrideFunction IdealBodyWeight() As Decimal”

The same error is present for the MalePatient class.

The warning is spot on, and I’ll do exactly what the IDE suggests — add the Overrides keyword to allthe methods in the MalePatient and FemalePatient classes (see Figure 2-5).

Figure 2-5

So I started with some conditional code that was gender-specific and managed to replace it with aninheritance hierarchy. This kind of refactoring is called replacing conditional logic with polymorphism. Youcan take a look at Chapter 12 for more on polymorphism.

Public MustInherit Class Patient '... Public MustOverride Function DailyCaloriesRecommended() _ As Decimal

Public Class MalePatient Inherits Patient

Public Overrides Function DailyCaloriesRecommended() _ As Decimal Return 66 + (6.3 * WeightInPounds) + _ (12.9 * HeightInInches) - (6.8 * Age) End Function '...

Public Class FemalePatient Inherits Patient

Public Overrides Function DailyCaloriesRecommended() _ As Decimal Return 655 + (4.3 * WeightInPounds) + _ (4.7 * HeightInInches) - (4.7 * Age) End Function

'...

If Patient Male, methodimplemented in

MalePatient

If Patient Female, methodimplemented in FemalePatient

39

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 39

Page 75: Professional Refactoring in Visual Basic

Removing Gender-Specific Code from the DistanceFromIdealWeight Method

One more step is needed to eliminate the last error present in the code. Since theDistanceFromIdealWeight method refers to IdealBodyWeightMan in the MalePatientclass and to IdealBodyWeightWoman in the FemalePatient class, it has to be changed so that it refers to the IdealBodyWeight method. In the previous section I eliminated gender-specific methods; now, in both classes, I modify the method DistanceFromIdealWeight so it looks the same:

Public Overrides Function DistanceFromIdealWeight() As DecimalReturn WeightInPounds - IdealBodyWeight()

End Function

Looking at the Patient Classes HierarchyNow it’s time to have a look at the complete hierarchy code. I end up with the classes looking like this(Listing 2-7).

Listing 2-7: Patient Classes Hierarchy

Public Class FemalePatientInherits Patient

Public Overrides Function DailyCaloriesRecommended() As DecimalReturn 655 + (4.3 * WeightInPounds) + _(4.7 * HeightInInches) - (4.7 * Age)

End Function

Public Overrides Function IdealBodyWeight() As DecimalReturn (45.5 + (2.3 * (HeightInInches - 60))) * 2.2046

End FunctionEnd Class

Public Class MalePatientInherits Patient

Public Overrides Function DailyCaloriesRecommended() As DecimalReturn 66 + (6.3 * WeightInPounds) + _(12.9 * HeightInInches) - (6.8 * Age)

End Function

Public Overrides Function IdealBodyWeight() As DecimalReturn (50 + (2.3 * (HeightInInches - 60))) * 2.2046

End FunctionEnd Class

Public MustInherit Class PatientPrivate ssnValue As StringPrivate firstNameValue As String

40

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 40

Page 76: Professional Refactoring in Visual Basic

Listing 2-7: Patient Classes Hierarchy (continued)

Private lastNameValue As StringPrivate heightInInchesValue As DecimalPrivate weightInPoundsValue As DecimalPrivate ageValue As IntegerPrivate genderValue As Gender

Public Property Gender() As GenderGet

Return genderValueEnd GetSet(ByVal Value As Gender)

genderValue = ValueEnd Set

End PropertyPublic Property SSN() As String

GetReturn ssnValue

End GetSet(ByVal Value As String)

ssnValue = ValueEnd Set

End PropertyPublic Property FirstName() As String

GetReturn firstNameValue

End GetSet(ByVal Value As String)

firstNameValue = ValueEnd Set

End PropertyPublic Property LastName() As String

GetReturn lastNameValue

End GetSet(ByVal Value As String)

lastNameValue = ValueEnd Set

End PropertyPublic Property HeightInInches() As Decimal

GetReturn heightInInchesValue

End GetSet(ByVal Value As Decimal)

heightInInchesValue = ValueEnd Set

End PropertyPublic Property WeightInPounds() As Decimal

GetReturn weightInPoundsValue

End GetContinued

41

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 41

Page 77: Professional Refactoring in Visual Basic

Listing 2-7: Patient Classes Hierarchy (continued)

Set(ByVal Value As Decimal)weightInPoundsValue = Value

End SetEnd PropertyPublic Property Age() As Integer

GetReturn ageValue

End GetSet(ByVal Value As Integer)

ageValue = ValueEnd Set

End Property

Public MustOverride Function DailyCaloriesRecommended() _As Decimal

Public MustOverride Function IdealBodyWeight() As Decimal

Public Function DistanceFromIdealWeight() As DecimalReturn WeightInPounds - IdealBodyWeight()

End FunctionEnd Class

This is a large listing, but notice that no comments are really necessary. The design is simple and theintent is understandable from the names used for the classes’ methods and properties. Now it’s time to go back to the FrmCaloriesCalculator class and make use of the newly created classes.

The BtnCalculate_Click Method Using a Patient Classes HierarchyTo complete the latest changes I now need to modify the client, the FrmCaloriesCalculator class. Inthe BtnCalculate_Click method I should create the correct subclass of the Patient hierarchy, basedon user input and the value of the gender radio button. This is demonstrated in Listing 2-8.

Listing 2-8: The BtnCalculate_Click Method, After the MalePatient and FemalePatientClasses Are Identified

Private Sub BtnCalculate_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnCalculate.Click

ClearResults()If ValidateUserInput() = False Then Return‘Making use of MalePatient and FemalePatient classes: ‘creating new instanceIf (RbtnMale.Checked) Then

Me.Patient = New MalePatientElse

Me.Patient = New FemalePatientEnd IfPatient.HeightInInches = _(TxtFeet.Text * 12) + TxtInches.TextPatient.WeightInPounds() = TxtWeight.TextPatient.Age = TxtAge.Text

42

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 42

Page 78: Professional Refactoring in Visual Basic

Listing 2-8: The BtnCalculate_Click Method, After the MalePatient and FemalePatientClasses Are Identified (continued)

TxtCalories.Text = Patient.DailyCaloriesRecommendedTxtIdealWeight.Text = Patient.IdealBodyWeightTxtDistance.Text = Patient.DistanceFromIdealWeight

End Sub

This looks a lot better. I’d say I’m almost done. Still, before calling it a day, I notice two details.

Since gender information is now represented by subclasses, I have no need for a Gender property in thePatient class. If you look carefully, you’ll see it is not used in any of the other classes. I can eliminate it.This leaves the Gender enumeration redundant. Dead code is yet another smell I need to remedy, so I’lleliminate the Gender enumerator in addition to the Gender property. I call this eliminate dead code refac-toring. Eliminating dead code is discussed in more detail in Chapter 7.

“Pulling Up” the DistanceFromIdealWeight Method When I made modifications to the body of the DistanceFromIdealWeight method in the FemalePatientand MalePatient classes, I realized one detail. Both methods ended up looking the same:

Public Overrides Function DistanceFromIdealWeight() As DecimalReturn WeightInPounds - IdealBodyWeight()

End Function

This is actually duplicate code, a smell we must try to eliminate. The solution is to move the methodimplementation back to the Patient class. I’ll eliminate the method from the FemalePatient andMalePatient classes and copy it back to the Patient class:

Public Function DistanceFromIdealWeight() As DecimalReturn WeightInPounds - IdealBodyWeight()

End Function

Pulling methods and properties up in the hierarchy is another type of refactoring that is very useful ineliminating duplicated code. It’s generally referred to as pull-up method or pull-up property refactoring. I deal with these refactorings in detail in Chapter 12.

I have just created an object-oriented structure for the domain-related logic and separated it from theGUI-related logic. I have also grouped patient data inside a single structure, and this is exactly the data Ineed to persist. So all this work has prepared a good starting point for the implementation of persistencefunctionality in the application, which is the subject of the next section.

Implementing the Persistence FunctionalityGenerally, when I program, I refactor all the time. Each time I add a method or a property, or resolve a bug, I take a look at the code, searching for the code smells. In this specific case the application wasvery simple, and I was postponing refactoring for a while. So the smells accumulated, and I had todedicate more time to refactoring procedures in a single go. Normally, refactoring is an integral part of the coding process.

43

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 43

Page 79: Professional Refactoring in Visual Basic

By any account, the time used to refactor the application was well spent. Now the code is better organizedand a lot easier to read, comprehend, and maintain. Since it is more modular, changes I apply from now onwill be more isolated and will have a more limited effect. Also, I have managed to group and gather thedata related to the patient — again, exactly the data I need to persist — into a single structure, simplifyingthe job of implementing this functionality.

The client has given me a blank slate as to how to implement the persistence functionality, and there are anumber of ways I can go about saving patients’ history: I can use a database like Access, a textual file, even an Excel worksheet. Since .NET has very good support for XML, I have decided to save patient history into anXML file. This will save me some work during the implementation, and given that XML is also easily readableby humans, it can save me even more work in implementing GUI elements for viewing the data.

Saving the DataAs usual, I start off by filling in the event-handling methods. Remembering the recent experience I hadwith the application, I decide to put data-validation code in a separate ValidatePatientPersonalDataprivate method, much like the existing ValidateUserInput method.

Validating a Patient’s Personal DataThis ValidatePatientPersonalData method is called from the BtnSave_Click method, and it checksthat a valid Social Security number and last and first name are entered. It looks just as you would expect:

Private Function ValidatePatientPersonalData() As BooleanIf Not IsNumeric(TxtSSNFirstPart.Text) Or _Not IsNumeric(TxtSSNSecondPart.Text) Or _Not IsNumeric(TxtSSNThirdPart.Text) Then

MsgBox(“You must enter valid SSN!”)Return False

End IfIf Not TxtFirstName.Text.Trim.Length > 0 Then

MsgBox(“You must enter patient’s first name!”)Return False

End IfIf Not TxtLastName.Text.Trim.Length > 0 Then

MsgBox(“You must enter patient’s last name!”)Return False

End IfReturn True

End Function

Next I must make sure that the calculation has been performed before I save the measurement. For thatpurpose it’s easiest if I directly call the BtnCalculate_Click method. It will check the validity of thedata, create a patient instance, perform the calculation, and visualize it on the form.

Creating the XML File Used for Patient Data Persistence Now I must take care of persisting the measurement to the file. Since I am not going to distribute a sepa-rate XML file, I must create the file programmatically. So, when saving the data, first I must check for theexistence of the XML file. If it exists, I want to append the data so the existing history is not lost. In thiscase I also need to look for the previous measurements by the same patient. It is best if I structure theXML so all measurements by one patient are under the same patient element.

44

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 44

Page 80: Professional Refactoring in Visual Basic

For that purpose I must define the format of the XML file. The format I am going to use is shown inListing 2-9.

Listing 2-9: The Patient-History XML File Format

<patientsHistory><patient ssn=”33-333-3333” firstName=”John” lastName=”Doe”>

<measurement date=”05/27/2006”><height>72</height><weight>210</weight><age>23</age><dailyCaloriesRecommended>1043.4</dailyCaloriesRecommended><idealBodyWeight>205.37988</idealBodyWeight><distanceFromIdealWeight>4.62012</distanceFromIdealWeight><!--Another measurement -->

</measurement></patient><!--Another patient -->

</patientsHistory>

I’ll save the XML file in the same place as the executable.

Now it’s time to churn out the code. You can take a look at code created to persist patient data inListing 2-10.

Listing 2-10: The BtnSave_Click Method Containing Persistence-Related Code

Private Sub BtnSave_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnSave.Click

If Not ValidatePatientPersonalData() Then Return

If patientValue Is Nothing ThenBtnCalculate_Click(Nothing, Nothing)

End If

Dim document As New XmlDocumentDim fileCreated As Boolean = TrueTry

document.Load(System.Reflection.Assembly. _GetExecutingAssembly.Location.Replace( _“DoctorsOrders.exe”, “patientHistory.xml”))

Catch noFile As IO.FileNotFoundException‘If file not found, set fileCreated to false and continuefileCreated = False

End Try If Not fileCreated Then

document.LoadXml(“<patientsHistory>” + _“<patient ssn=”“” + patientValue.SSN + “”“” + _“ firstName=”“” + patientValue.FirstName + “”“” + _“ lastName=”“” + patientValue.LastName + “”“>” + _

“<measurement date=”“” + DateTime.Today + “”“>” + _“<height>” + patientValue.HeightInInches.ToString + _“</height>” + _

Continued

45

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 45

Page 81: Professional Refactoring in Visual Basic

Listing 2-10: The BtnSave_Click Method Containing Persistence-Related Code (continued)

“<weight>” + patientValue.WeightInPounds.ToString + _“</weight>” + _“<age>” + patientValue.Age.ToString + “</age>” + _“<dailyCaloriesRecommended>” + _patientValue.DailyCaloriesRecommended.ToString + _“</dailyCaloriesRecommended>” + _“<idealBodyWeight>” + _patientValue.IdealBodyWeight.ToString + _“</idealBodyWeight>” + _“<distanceFromIdealWeight>” + _patientValue.DistanceFromIdealWeight.ToString + _“</distanceFromIdealWeight>” + _“</measurement>” + _“</patient>” + _“</patientsHistory>”)

Else‘Search for existing node for this patientDim patientNode As XmlNode = Nothing For Each node As XmlNode _ In document.FirstChild.ChildNodes

For Each attrib As XmlAttribute _ In node.Attributes

‘We will use SSN to _ uniquely identify patient If (attrib.Name = “ssn” And _ attrib.Value = patientValue.SSN) Then

patientNode = node Exit For

End If Next

Next If patientNode Is Nothing Then

‘just clone any patient node _and use it for the newDim thisPatient As XmlNode = _document.DocumentElement. _FirstChild.CloneNode(True)thisPatient.Attributes(“ssn”).Value = _patientValue.SSNthisPatient.Attributes(“firstName”).Value = _patientValue.FirstNamethisPatient.Attributes(“lastName”).Value = _patientValue.LastNameDim measurement As XmlNode = _thisPatient.FirstChildmeasurement.Attributes(“date”).Value = _DateTime.Todaymeasurement.Item(“height”).FirstChild.Value = _patientValue.HeightInInchesmeasurement.Item(“weight”).FirstChild.Value = _

46

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 46

Page 82: Professional Refactoring in Visual Basic

Listing 2-10: The BtnSave_Click Method Containing Persistence-Related Code (continued)

patientValue.WeightInPoundsmeasurement.Item(“age”).FirstChild.Value = _patientValue.Agemeasurement.Item(“dailyCaloriesRecommended”). _FirstChild.Value = _patientValue.DailyCaloriesRecommendedmeasurement.Item(“idealBodyWeight”). _FirstChild.Value = patientValue.IdealBodyWeightmeasurement.Item(“distanceFromIdealWeight”). _FirstChild.Value = _patientValue.DistanceFromIdealWeightdocument.FirstChild.AppendChild(thisPatient)

Else‘If patient node found just clone any measurement‘and use it for the new measurementDim measurement = patientNode.FirstChild. _ CloneNode(True) measurement.Attributes(“date”).Value = _DateTime.Todaymeasurement.Item(“height”).FirstChild.Value = _patientValue.HeightInInchesmeasurement.Item(“weight”).FirstChild.Value = _patientValue.WeightInPoundsmeasurement.Item(“age”).FirstChild.Value = _patientValue.Agemeasurement.Item(“dailyCaloriesRecommended”). _FirstChild.Value = _patientValue.DailyCaloriesRecommendedmeasurement.Item(“idealBodyWeight”). _FirstChild.Value = patientValue.IdealBodyWeightmeasurement.Item(“distanceFromIdealWeight”). _FirstChild.Value = _patientValue.DistanceFromIdealWeightpatientNode.AppendChild(measurement)

End IfEnd If‘Finally, save the xml to filedocument.Save(System.Reflection.Assembly. _GetExecutingAssembly.Location.Replace( _“DoctorsOrders.exe”, “patientHistory.xml”))

End Sub

This code doesn’t look so complicated. Nevertheless, let me address it with a few comments.

The BtnSave_Click method never checks for file existence explicitly. Instead, a FileNotFoundExceptionis caught and a flag set that indicates that this is probably the first time a patient history is to be saved. Inthat case the XML structure is created by concatenating and loading a string representing the XML structureas a way to get around the rather verbose DOM API.

47

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 47

Page 83: Professional Refactoring in Visual Basic

In case the file exists, the code searches for this specific patient entry by comparing Social Security num-bers. If the patient entry is not present, it is created by the cloning of any patient’s node, thus saving thetrouble of using the DOM API to create it. Since the file exists, we can assume at least one entry exists.

If the patient is found, a new measurement entry is created by the cloning of the measurement node.Since the patient node is found, it is safe to assume at least one measurement was saved.

While this time I was careful to place data-validation code in a separate function, I still ended up with avery, very long method. I dare say this is an excellent specimen of what is generally better known by thename spaghetti code. So, before I am told to change my career in the direction of Italian cuisine, I had bet-ter do something about refactoring this method.

Method Decomposition RevisitedIn studying the method, one thing that might really bother your eye is that there are two completelyidentical pieces of code involved in setting measurement values to XmlNode. This code can be movedinto a new method called SetMeasurementValues:

Private Function SetMeasurementValues( _ByVal measurement As XmlNode) As XmlNode

measurement.Attributes(“date”).Value = _DateTime.Todaymeasurement.Item(“height”).FirstChild.Value = _patientValue.HeightInInchesmeasurement.Item(“weight”).FirstChild.Value = _patientValue.WeightInPoundsmeasurement.Item(“age”).FirstChild.Value = _patientValue.Agemeasurement.Item(“dailyCaloriesRecommended”). _FirstChild.Value = _patientValue.DailyCaloriesRecommendedmeasurement.Item(“idealBodyWeight”). _FirstChild.Value = patientValue.IdealBodyWeightmeasurement.Item(“distanceFromIdealWeight”). _FirstChild.Value = _patientValue.DistanceFromIdealWeight

End Function

Also, I can further decompose the BtnSave_Click method, much as I did BtnCalculate_Click. Newmethods are created: LoadPatientHistoryFile, CreateXmlDocumentFirstTime, FindPatientNode,and AddNewPatient. You can see those methods in Listing 2-11.

Listing 2-11: Decomposing the BtnSave_Click Method

Private Function LoadPatientHistoryFile() As XmlDocumentDim document As New XmlDocumentdocument.Load(System.Reflection.Assembly. _GetExecutingAssembly.Location.Replace( _“DoctorsOrders.exe”, “patientHistory.xml”))Return document

End Function

Private Function CreateXmlDocumentFirstTime() As XmlDocument

48

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 48

Page 84: Professional Refactoring in Visual Basic

Listing 2-11: Decomposing the BtnSave_Click Method (continued)

Dim document As New XmlDocumentdocument.LoadXml(“<patientsHistory>” + _

“<patient ssn=”“” + patientValue.SSN + “”“” + _“ firstName=”“” + patientValue.FirstName + “”“” + _“ lastName=”“” + patientValue.LastName + “”“>” + _

“<measurement date=”“” + DateTime.Today + “”“>” + _“<height>” + patientValue.HeightInInches.ToString + _

“</height>” + _“<weight>” + patientValue.WeightInPounds.ToString + _“</weight>” + _“<age>” + patientValue.Age.ToString + “</age>” + _“<dailyCaloriesRecommended>” + _patientValue.DailyCaloriesRecommended.ToString + _“</dailyCaloriesRecommended>” + _“<idealBodyWeight>” + _patientValue.IdealBodyWeight.ToString + _“</idealBodyWeight>” + _“<distanceFromIdealWeight>” + _patientValue.DistanceFromIdealWeight.ToString + _“</distanceFromIdealWeight>” + _“</measurement>” + _“</patient>” + _

“</patientsHistory>”)Return document

End Function

Private Function FindPatientNode(ByVal document _As XmlDocument) As XmlNode

Dim patientNode As XmlNode = NothingFor Each node As XmlNode In document.FirstChild.ChildNodes

For Each attrib As XmlAttribute In node.Attributes‘We will use SSN to uniquely identify patientIf (attrib.Name = “ssn” And _attrib.Value = patientValue.SSN) Then

patientNode = nodeEnd If

NextNextReturn patientNode

End Function

Private Function AddNewPatient(ByVal document _As XmlDocument) As XmlDocument

‘just clone any patient node and use it for newDim thisPatient As XmlNode = _document.DocumentElement.FirstChild.CloneNode(True)thisPatient.Attributes(“ssn”).Value = _patientValue.SSNthisPatient.Attributes(“firstName”).Value = _patientValue.FirstNamethisPatient.Attributes(“lastName”).Value = _patientValue.LastName

Continued

49

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 49

Page 85: Professional Refactoring in Visual Basic

Listing 2-11: Decomposing the BtnSave_Click Method (continued)

Dim measurement As XmlNode = _thisPatient.FirstChildmeasurement = SetMeasurementValues(measurement)document.FirstChild.AppendChild(thisPatient)

End Function

You have just witnessed a very immediate benefit of method extraction. Because I am able to extract twoidentical pieces of code into the method SetMeasurementValues, I have managed to eliminate duplica-tion in the code and reduce total line count in a significant way.

Finding the Right Place for MethodsAs in the aftermath of the BtnCalculate_Click method decomposition, I’m getting the feeling thatthese data-persistence methods do not really belong to the FrmCaloriesCalculator class. Rather, theyare interested in patient data, so at this point I am tempted to move them to a Patient class. So what doyou think — is that the right move at this moment? Or are there arguments against moving newly cre-ated LoadPatientHistoryFile, CreateXmlDocumentFirstTime, FindPatientNode, andAddNewPatient methods to the Patient class?

The Patient class is performing medical calculations. So far, it has nothing to do with managing themeasurements of different patients. If I were to move these new methods to the Patient class, it wouldunnecessarily encumber this class with persistence code. Some future client of our Patient class, inter-ested in the calculation that this class provides, might prefer persisting data in some other form — adatabase, for example. If I were to move XML persistence methods to the Patient class I would beobliged to deliver this functionality as well. Now imagine I discover a bug in my persistence code. Iwould need to distribute a new version to the client just in case that client uses XML persistence.

The reasoning I have just used is expressed in more generic terms as the single responsibility principle(SRP), and I discuss this design principle in Chapter 11.

Extracting a New Persistence ClassAs the arguments in the previous section show, I need a new class in the project. I’ll call itPatientHistoryXMLStorage, and I’ll move all the new persistence-related methods fromFrmCaloriesCalculator to this class. This is another example of move method refactoring, and it is an integral part of the more complex extract class refactoring, discussed in Chapter 12.

Now, once I have all those methods in PatientHistoryXMLStorage, I soon observe that they all referencea patient instance. To make the code compile, the methods need to somehow get hold of a patient object.

There are two possible ways to do this:

❑ I can add a parameter to each and every method except LoadPatientHistoryFile

❑ I can add a field named patientValue to the class

As you can guess, the second option requires me to write a lot less code. That way, a patientValue willform part of the PatientHistoryXMLStorage state. So I’ll go for that solution.

50

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 50

Page 86: Professional Refactoring in Visual Basic

While I’m at it, there is another parameter that is repeated in quite a few methods. It is the documentparameter. Again, I can make this parameter a field of the PatientHistoryXMLStorage class. In order tokeep names consistent, I change the name to documentValue. FindPatientNode needs to be adjusted soit references documentValue instead of document.

In addition to this change, AddNewPatient, LoadPatientHistoryFile, andCreateXmlDocumentFirstTime are not returning values any more, so their declaration is changed and now looks like this:

Private Sub LoadPatientHistoryFile()Private Sub AddNewPatient()Private Sub CreateXmlDocumentFirstTime()

Also, the fields in the class now look like this:

Private patientValue As PatientPrivate documentValue As XmlDocument = New XmlDocument

All I need at this point is the public method in the PatientHistoryXMLStorage class that the clientcode could use.

Going back to FrmCaloriesCalculator class, you can see that the BtnSave_Click event handler contains exactly the code I need. I’ll extract the Save method from this routine and move it toPatientHistoryXMLStorage:

Public Function Save(ByVal patient As Patient)patientValue = patientDim fileCreated As Boolean = TrueTry

LoadPatientHistoryFile()Catch noFile As IO.FileNotFoundException

fileCreated = FalseEnd TryIf Not fileCreated Then

CreateXmlDocumentFirstTime()Else

Dim patientNode As XmlNode = FindPatientNode()If patientNode Is Nothing Then

AddNewPatient()Else

‘just clone any measurement and use it for newDim measurement As XmlNode = _patientNode.FirstChild.CloneNode(True)measurement = SetMeasurementValues(measurement)patientNode.AppendChild(measurement)

End IfEnd IfdocumentValue.Save(System.Reflection.Assembly. _GetExecutingAssembly.Location.Replace( _“DoctorsOrders.exe”, “patientHistory.xml”))

End Function

51

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 51

Page 87: Professional Refactoring in Visual Basic

This leaves me with the BtnSave_Click event handler looking like this:

Private Sub BtnSave_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnSave.Click

If Not ValidatePatientPersonalData() Then ReturnBtnCalculate_Click(Nothing, Nothing)storage.Save(patientValue)

End Sub

In case you are wondering where the storage variable came from, it is now another field in theFrmCaloriesCalculator class, and its declaration looks like this:

Private storage As PatientHistoryXMLStorage = _New PatientHistoryXMLStorage

With this, I have effectively implemented persistence functionality in a separate, persistence-dedicated class.

Implementing Patient-History Display Functionality Finally, I have only one functionality pending — to somehow visualize patient data. Internet Explorer does a pretty good job at visualizing XML data, so instead of adding a new form to my project I am simplygoing to open the Internet Explorer instance, pass the location of the patientHistory.xml file to it, andlet it display the file content. If I wanted to improve on this, I could write my own XSL template and visual-ize the data by using Internet Explorer’s built-in XSL engine, but at this point I’ll stick with IE’s defaultXML stylesheet.

Calling upon Internet Explorer is easily done through the Shell function from the Microsoft.VisualBasic namespace. All I need now is the location of the file. In the Save method of thePatientHistoryXMLStorage class, I programmed the application so it saves the XML file next to aCalories Calculator exe. I could easily copy-paste this line, but this would mean creating a duplicateentry in our code. As a matter of fact, I already have duplicated code because I used the same line in the LoadPatientHistoryFile method.

Instead of copying the code — and you can read about all the headaches duplicated code can give you in Chapter 9 — I decide to add a public field to the PatientHistoryXMLStorage class. Since I need toaccess it easily from another class, I’ll make this field static using the Shared keyword. Here it is:

Public Shared patientHistoryXmlFile = _System.Reflection.Assembly. _GetExecutingAssembly.Location.Replace( _“DoctorsOrders.exe”, “patientHistory.xml”)

I also manage to simplify LoadPatientHistoryFile. Now it looks like this:

Private Sub LoadPatientHistoryFile()documentValue.Load(patientHistoryXmlFile)

End Sub

Also, the related line in the Save method now looks like this:

documentValue.Save(patientHistoryXmlFile)

52

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 52

Page 88: Professional Refactoring in Visual Basic

Finally, the BtnView_Click method looks like this:

Private Sub BtnView_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnView.Click

TryShell(IEExeFile + “ “ _+ PatientHistoryXMLStorage.patientHistoryXmlFile, _AppWinStyle.NormalFocus)

Catch noFile As IO.FileNotFoundExceptionMsgBox(“Internet Explorer not found”)

End TryEnd Sub

As you can see in the preceding code, and going along the same lines, I also replaced the Internet Explorerexecutable location literal with a static field in the FrmCaloriesCalculator class, IEExeFile:

Private Shared IEExeFile = _“C:\Program Files\Internet Explorer\IEXPLORE.EXE”

This is an example of replace magic number with symbolic constant refactoring. While in this case it was nota number that got replaced — I replaced a file location string literal — it is still just a variation of thisimportant type of refactoring. Check out Chapter 9 for more background on replace magic literal withconstant refactoring and the magic literals smell.

For the final code for the PatientHistoryXMLStorage class, take a look at Listing 2-12.

Listing 2-12: The Final Version of the PatientHistoryXMLStorage Class

Option Strict Off

Imports System.XmlImports System.IO

Public Class PatientHistoryXMLStorage

Private patientValue As PatientPrivate documentValue As XmlDocument = New XmlDocumentPublic Shared patientHistoryXmlFile = _System.Reflection.Assembly. _GetExecutingAssembly.Location.Replace( _“CaloriesCalculator.EXE”, “patientHistory.xml”)

Private Function SetMeasurementValues( _ByVal measurement As XmlNode) As XmlNode

measurement.Attributes(“date”).Value = _DateTime.Todaymeasurement.Item(“height”).FirstChild.Value = _patientValue.HeightInInchesmeasurement.Item(“weight”).FirstChild.Value = _patientValue.WeightInPoundsmeasurement.Item(“age”).FirstChild.Value = _patientValue.Age

Continued

53

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 53

Page 89: Professional Refactoring in Visual Basic

Listing 2-12: The Final Version of the PatientHistoryXMLStorage Class (continued)

measurement.Item(“dailyCaloriesRecomended”). _FirstChild.Value = _patientValue.DailyCaloriesRecomendedmeasurement.Item(“idealBodyWeight”). _FirstChild.Value = patientValue.IdealBodyWeightmeasurement.Item(“distanceFromIdealWeight”). _FirstChild.Value = _patientValue.DistanceFromIdealWeightReturn measurement

End Function

Private Sub LoadPatientHistoryFile()documentValue.Load(patientHistoryXmlFile)

End Sub

Private Sub CreateXmlDocumentFirstTime()documentValue.LoadXml(“<patientsHistory>” + _

“<patient ssn=”“” + patientValue.SSN + “”“” + _“ firstName=”“” + patientValue.FirstName + “”“” + _“ lastName=”“” + patientValue.LastName + “”“>” + _

“<measurement date=”“” + DateTime.Today + “”“>” + _“<height>” + patientValue.HeightInInches.ToString + _

“</height>” + _“<weight>” + patientValue.WeightInPounds.ToString + _“</weight>” + _“<age>” + patientValue.Age.ToString + “</age>” + _“<dailyCaloriesRecomended>” + _patientValue.DailyCaloriesRecomended.ToString + _“</dailyCaloriesRecomended>” + _“<idealBodyWeight>” + _patientValue.IdealBodyWeight.ToString + _“</idealBodyWeight>” + _“<distanceFromIdealWeight>” + _patientValue.DistanceFromIdealWeight.ToString + _“</distanceFromIdealWeight>” + _“</measurement>” + _“</patient>” + _

“</patientsHistory>”)End Sub

Private Function FindPatientNode() As XmlNodeDim patientNode As XmlNode = NothingFor Each node As XmlNode In documentValue.FirstChild.ChildNodes

For Each attrib As XmlAttribute In node.Attributes‘We will use SSN to uniquely identify patientIf (attrib.Name = “ssn” And _attrib.Value = patientValue.SSN) Then

patientNode = nodeEnd If

NextNextReturn patientNode

54

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 54

Page 90: Professional Refactoring in Visual Basic

Listing 2-12: The Final Version of the PatientHistoryXMLStorage Class (continued)

End Function

Private Sub AddNewPatient()‘just clone any patient node and use it for newDim thisPatient As XmlNode = _documentValue.DocumentElement.FirstChild.CloneNode(True)thisPatient.Attributes(“ssn”).Value = _patientValue.SSNthisPatient.Attributes(“firstName”).Value = _patientValue.FirstNamethisPatient.Attributes(“lastName”).Value = _patientValue.LastNameDim measurement As XmlNode = _thisPatient.FirstChildmeasurement = SetMeasurementValues(measurement)documentValue.FirstChild.AppendChild(thisPatient)

End Sub

Public Sub Save(ByVal patient As Patient)patientValue = patientDim fileCreated As Boolean = TrueTry

LoadPatientHistoryFile()Catch noFile As IO.FileNotFoundException

fileCreated = FalseEnd TryIf Not fileCreated Then

CreateXmlDocumentFirstTime()Else

Dim patientNode As XmlNode = FindPatientNode()If patientNode Is Nothing Then

AddNewPatient()Else

‘just clone any measurement and use it for newDim measurement As XmlNode = _patientNode.FirstChild.CloneNode(True)measurement = SetMeasurementValues(measurement)patientNode.AppendChild(measurement)

End IfEnd IfdocumentValue.Save(patientHistoryXmlFile)

End SubEnd Class

It is a rather long listing, but it is here for a purpose. Before this book ends, I will have another go at thiscode from the Visual Basic 2008 perspective. (If you can’t wait to see what happens to this code in the VB2008 version, take a look at Chapter 15.) With this, I have concluded implementing the latest requirementsof the Calories Calculator application.

55

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 55

Page 91: Professional Refactoring in Visual Basic

Calories Calculator, Refactored VersionNow it’s time to take a look at the new structure of the application. A class diagram (Figure 2-6) can giveyou a good overview of the code I ended up with.

Figure 2-6

I now have a lot more classes and methods than when I started. The impression is that the code is morebalanced and better organized, with each class dedicated to a single and specific purpose. I have alsoused inheritance, possibly opening the door for future extensions.

What does this tell you? What could you observe during the process of developing this sample applica-tion? Have I managed to improve the code as I implemented the requirements? What are the benefits ofall this?

Take a look at some of the answers (Table 2-1 and the list that follows).

Table 2-1: Refactoring Techniques Applied and Benefits Obtained

Refactoring Technique Result

Extract method Atomic, more reusable methods with clear purpose

Extract class Single-purpose classes improve reuse, maintainability, andreadability of code

Move method Better structured code when used in conjunction with extract class

Reduce method visibility Improves encapsulation, thus making code simpler and more clear-cut

FemalePatient

+DailyCaloriesRecommended+IdealBodyWeight()

MalePatient

+DailyCaloriesRecommended+IdealBodyWeight()

FrmCaloriesCalculator

-BtnCalculate_Click()-BtnSave_Click()-BtnView_Click()-ValidateUserInput()-ValidatePatientPersonalData()-ClearResults()

Patient+SSN+FirstName+LastName+HeightInInches+Weight+Age+DailyCaloriesRecommended+IdealBodyWeight()+DistanceFromIdealWeight()

PatientHistoryXMLStorage+Document+Save(in patient : Patient)+LoadPatientHistoryFile()+CreateXMLDocumentFirstTime()+AddNewPatient()+FindPatientNode()+SetMeasurement()

56

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 56

Page 92: Professional Refactoring in Visual Basic

Table 2-1: Refactoring Techniques Applied and Benefits Obtained (continued)

❑ Initially I decomposed the long methods; this improved the code in several ways. By givingexplicit names to pieces of code I improved the readability of the code and made it simpler. It iseasier to understand the purpose of each method; the intent is explicitly stated, and so the main-tainability of the code has been improved. Any new programmer starting out on the code willneed less time to get acquainted with it. Even experienced programmers can forget a specificdetail of the code they developed, so they will benefit from improved comprehensibility.

❑ By making the code more granular and structured, I improved the reuse potential of the code.This simplifies the code by making the code-base smaller. I ended up with less code.

❑ Reuse potential was also improved when I extracted the SetMeasurementValues method. This immediately resulted in another benefit. I replaced another piece of code with the call tothis method, thus eliminating duplication in the code. This improves the maintainability of thecode, because if ever I need to modify some behavior, I need only perform the modification in a single place. This results in code less prone to bugs, as I am less likely to face a situation inwhich I modified behavior in one place but failed to modify it in another place containing thesame duplicated code.

❑ By eliminating code that ended up unused, I made the application easier to read and to debug,improving simplicity of the code. With this refactoring, I have reduced the overall quantity ofthe code.

❑ I have managed to improve maintainability of the code by replacing literals scattered through-out the code with a constant. This way, if a value ever needs to be changed, the change can beperformed in a single place.

❑ Extracting new classes also improves the reuse in the code, and organizing classes in a hierarchyagain helped me avoid duplication, but on a different scale. At the same time, extensibility of thecode was enhanced. Now that I have the MalePatient and FemalePatient classes extending the Patient class, it is not so far-fetched to imagine, for example, ChildPatient as an addition to the hierarchy. This means I can add more behavior without modifying existing code — meaningless to test, to recompile, or to redeploy.

That about wraps it up for this demonstration. In the following chapters we’ll study refactoring tech-niques in greater detail.

Refactoring Technique Result

Replace conditional logic with polymorphism

Reduces duplication in the code, improving maintainabilityand extensibility of the code

Pull-up method Reduces duplication in code

Eliminate dead code Simplifies code and makes it easier to read

Replace magic number with symbolic constant

Code is easier to maintain, because the changes need beapplied only in a single place

57

Chapter 2: A First Taste of Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 57

Page 93: Professional Refactoring in Visual Basic

SummaryVisual Basic programmers typically like to dip into the code right from a project’s outset. This no-nonsense,goal-oriented approach can have a lot of benefits, allowing for early prototyping and increased early pro-ductivity. However, as the application progresses and new requirements appear, it becomes necessary torestructure the code so the optimum design is maintained.

In this chapter I tried to follow this typical approach. In the example, I constructed a simple applicationthat can perform some medical calculations, like estimating ideal body weight and a recommended daily amount of calories. Initially, I didn’t put too much emphasis on the design and quality of the code.However, as I responded to growing requirements from the client, I also spent some time on refactoringthe code in order to keep it in shape and proved how this kind of refactoring pays off.

This example has shown the ways refactoring improves the design and makes code simpler and easier to understand. Making methods more granular improved the reusability of the code. I also improvedreusability by making each class responsible for a single task. Eliminating duplications in the code madeit smaller and easier to modify. Organizing classes into a logical hierarchy also dealt with duplicate codeand made the code more extensible. Refactored source is easier to reuse, extend, and maintain. It is lessprone to bugs and easier to optimize.

With this example I tried to provide insight into how individual refactoring techniques work togethertoward improving the design of the code. With this practical demonstration, a general context for indi-vidual refactoring techniques should become apparent. This should help you understand where each ofthese refactoring techniques fits inside the global picture and should prepare you for examining each of these techniques in detail.

While some of the refactoring techniques I demonstrated can be performed much more efficiently by meansof an automated refactoring tool, in this chapter I tried to stay focused on the code and the refactoring tech-niques as they are performed manually. In the next chapter I’ll talk about the tools that can help you refactormuch more efficiently and securely. I’ll also talk about a refactoring toolkit and especially about an auto-mated refactoring tool.

58

Part I: Introduction to Refactoring

79796c02.qxd:WroxPro 2/25/08 8:56 AM Page 58

Page 94: Professional Refactoring in Visual Basic

Assembling a Refactoring Toolkit

Many techniques and methodologies make you produce and maintain artifacts, in addition tosource code, as an integral part of the project — things like documentation, diagrams, and so on.Some approaches are very strict in controlling the way you code, imposing rules and guidelines thatconfine the way you program in order to make your code more uniform. In all these cases, you mayoften feel that an additional burden has encumbered this already demanding line of work. It caneasily make you reluctant to adopt new techniques. With refactoring, the rules are not cast in stone,nor do have you to deal with any additional baggage. On the contrary, the more expert you becomein refactoring techniques, the more confident you are with the code. What refactoring does is tomake you feel you are in charge. The productivity gain and coding proficiency that come fromrefactoring are further enhanced by the right set of tools.

When I talk about assembling a refactoring toolkit, which is the topic of this chapter, I consider the following three pieces of software mandatory, no matter the size of your team or project, yourmethodology, or the type of software you are making:

❑ An automated refactoring tool

❑ A unit-testing framework

❑ A source-code version-control system

Only one is directly related to performing refactoring transformations, while the other two are in myopinion indispensable for any type of serious refactoring work. The refactoring tool can automatecertain refactorings but only when it is used together with unit testing and source-code control willyou have the freedom and security to perform refactoring continuously and on a large scale.

In this chapter I’ll discuss the first two items in detail. Since I haven’t met many teams that don’tuse some kind of version-control system, I will not go into any great detail about it other than todedicate just enough space to treat version-control issues relevant to refactoring.

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 59

Page 95: Professional Refactoring in Visual Basic

As you will see, there is no single tool that encompasses all the functionality you need for a successfulrefactoring process. Visual Studio is on the road to providing a unified solution, but for the time beingyou have to make an effort to assemble this refactoring toolkit yourself.

If you would like to learn about refactoring techniques right away, you can skip this chapter and comeback to this discussion of tools later on, after you have read about each individual refactoring.

Using an Automated Refactoring ToolWider adoption of refactoring in any programming language has generally been influenced by the avail-ability of an automated refactoring tool. It is one thing to understand the theory, the pros and cons, andeven to be an eager devotee of the technique, and quite another to start meddling with your code in theface of an ever-closer deadline. Even the smallest change to your code can break it, so you need a reallycool head and audacity to vouch for some intangible characteristic like design and code quality whenthere are pending requirements waiting to be implemented.

Wouldn’t it be great if you had a tool with enough intelligence to know how to perform changes in your code without breaking it? For example, if you renamed a method, the tool would search for all theclient code that calls this method and replace its name in the client code. Or, if you extracted a piece ofcode and put it into a new method, the tool would make sure that all the necessary parameters wereincluded in the method declaration.

With Visual Studio 2008, there is such a tool available for Visual Basic programmers. While you mustdownload and install it separately, the basic version of the tool is free of charge.

With Visual Studio 2008 and 2005, VB developers can obtain the Refactor! Visual Studio add-in fromDeveloper Express (http://www.devexpress.com/vbrefactor) free of charge. Refactor! for VB sportsa nice set of basic refactorings, and the number of supported refactorings is growing. This is not the onlytool on the market. Even Developer Express has a more advanced pro version of Refactor!. Other toolsmight provide a larger number of refactorings and cross-language support. However, I find that once youlearn to use basic refactoring techniques, adopting new ones is not so difficult. And taking into accountthat Refactor! for VB is free, I decided to use it as a standard tool in this book.

Please note that Refactor! for VB does not work with the Visual Studio Express edition.

Once refactoring becomes an integral part of your development process, as I hope it will, you can testsome other tools that are available on the market and pick the one that best fits your taste and budget.The majority of these tools include free trial versions, so you will be able to test them before you buythem. The next sections touch on some of the tools that are available today.

ReSharper from JetBrainsJetBrains (www.jetbrains.com) made its name with the excellent IntelliJ IDEA Java IDE that broughtanother level of productivity to often nonresponsive Java IDEs, and pioneered comprehensive refactor-ing support for Java. Its first venture into the .NET arena is ReSharper, a refactoring and developer pro-ductivity add-in for Visual Studio. They started by supporting C#, hence the name, but ReSharper isnow a cross-language tool that supports Visual Basic XML, XAML, and ASP.NET.

60

Part I: Introduction to Refactoring

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 60

Page 96: Professional Refactoring in Visual Basic

ReSharper sports a number of useful refactoring transformations:

❑ Comprehensive, project-wide Rename refactoring

❑ Extract Method with dialog window preview

❑ Move Class refactoring

❑ Explicit Imports refactoring, called “Optimize Using” in ReSharper

❑ Use Base Type Where Possible refactoring

❑ Change Scope

It has an integrated NUnit test runner, advanced navigation and formatting capabilities, and a lot ofother interesting features that will make your programming life a lot easier.

Visual Assist X from Whole TomatoIt’s probably not the “whole tomato” of VB refactoring, since it is limited in the number of refactorings itoffers, but Visual Assist X still features some important VB refactorings, the first on the list being the all-important Extract Method. It is also able to perform Rename and Encapsulate Field refactoring. Like otherdeveloper productivity tools, it sports a number of navigation, advanced syntax highlighting, enhancedIntelliSense, spell checking, and autocomplete features. See www.wholetomato.com for more detail.

Refactor! Pro from Developer ExpressYou can pay to upgrade your free Refactor! to Refactor! Pro. Refactor! Pro includes over 150 refactor-ings and supports C#, VB, ASP.NET, C++, JavaScript, and more. It also works with previous editions of Visual Studio, namely 2002 and 2003. Further, it enables you to customize the way refactorings areperformed and even extend the tool by programming your own refactorings. Now you are going tosee how you can use Refactor! for VB, the basics of its user interface, and the list of refactorings thistool supports. Finally, you’ll see some advanced options that this tool provides but that are hiddenfrom the user at the first glance.

Getting Started with Refactor! Refactor! uses Visual Studio add-in architecture to integrate seamlessly into the Visual Studio environ-ment. The new features become apparent once you open a Visual Basic file for editing. There is also adocumentation section that gets merged with rest of the Visual Studio documentation when you installthe add-in.

There are four ways to activate Refactor! and invoke the operations it provides:

❑ Using smart tags

❑ Right-clicking

❑ Using the Ctrl+` (backtick) keyboard shortcut

❑ Using cut and paste

61

Chapter 3: Assembling a Refactoring Toolkit

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 61

Page 97: Professional Refactoring in Visual Basic

Using Smart TagsA smart tag takes the form of a small line under the first character in the identifier. When you place thecaret on some identifier in your code or select the piece of code, the smart tag appears, indicating a pos-sible refactoring that you can activate by opening the menu and selecting it from the list. Smart tags arecontext-sensitive, so you see only refactorings available for the selected code or for the identifier onwhich the caret is placed, and the smart tag will appear only if there are refactorings available at theposition indicated by the caret. You can see smart tag at the first line in the ClearResults method inFigure 3-1.

Figure 3-1

Right-Clicking the MouseYou can make the context menu appear by right-clicking on a certain identifier or selected piece of code inyour source-code editor. If you have the Refactor! add-in installed, a new option called Refactor! appears inthe menu. This happens only if there is a refactoring available for that identifier or selected portion of code.The item in the menu leads to a new submenu containing all the refactoring options available for that selec-tion. Again, the submenu content is filtered so only refactoring relevant to the selected code appears. Youcan see a context menu containing the Refactor! menu item in Figure 3-2.

Figure 3-2

62

Part I: Introduction to Refactoring

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 62

Page 98: Professional Refactoring in Visual Basic

Using the Keyboard Shortcut Ctrl+` (One-Key Refactoring)This key combination activates the refactoring mode in Visual Studio. First you have to place a caret over theidentifier or select the code related to the refactoring we are trying to perform. When you hit Ctrl+`, a menuappears with all refactorings available for that selection or caret position. However, this means of activatingRefactor! is different in one respect from right-clicking. It provides fast access to Refactor! functionality anddoes not require the use of a mouse, so in case a single refactoring is available for the active selection or caretposition, the refactoring is invoked immediately. No additional input or intermediate screen is presented.This is why it is also referred to as one-key refactoring. It is a good option once you become more comfortablewith the tool.

On the U.S. keyboard, the ` (backtick) key is the same as the ~ (tilde) key and is generally placed abovethe left tab key.

On some European keyboards this combination is not easy to hit, so you can rebind Refactor! to some other,more accessible key combination. In order to do this, open Tools ➪ Customize in Visual Studio and press theKeyboard... button. In the Options window define a new shortcut for the CodeRush.Refactor command.

Using Cut and PasteYou can use cut and paste to extract a method. Just cut the code that you want for the new method andpaste it inside the class between methods. Refactor! will add the appropriate calling code at the cut pointand build the method-signature wrapper around the code you paste. You can also introduce an explainingvariable with cut and paste. Just cut an expression to the clipboard and paste it on an empty line above thecut point. Refactor! will declare a new variable of the appropriate type and assign it the expression you’repasting. It will also add a reference to that variable at the cut point.

Now that you have seen how to invoke Refactor! from the Visual Studio IDE, I want to take a look at thebasic elements of Refactor! for the VB user interface.

Exploring Refactor! for the VB User InterfaceIn one way, Refactor! for VB is quite different from the rest of the tools on the market. In order to help pro-grammers always work at maximum speed and to have source code in front of them at all times, the creatorsof Refactor! have avoided placing modal windows in front of the user. Instead they created a set of newvisual features that let users exploit the Refactor! functionality. Those features are hints, markers, linkedidentifiers, target pickers, and replace progress indicators. All have very distinctive and colorful visual stylesand even some amusing animation effects. In this section I’ll walk through each of these features.

HintsUntil you really master Refactor! for VB, you will need some help in finding your way around it. Hintsserve exactly this purpose; they help you see all available options and locate newly created code. Thereare three types of hints:

❑ Action hints

❑ Big hints

❑ Shortcut hints

63

Chapter 3: Assembling a Refactoring Toolkit

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 63

Page 99: Professional Refactoring in Visual Basic

Action HintsAction hints appear after one-key refactoring is invoked and only one refactoring is available. They generallyhave the form of a large arrow pointing to a place where refactoring will happen. The arrow shows the nameof the refactoring that was applied. After a few seconds this type of hint disappears automatically. Figure 3-3shows an action hint.

Figure 3-3

Big HintsBig hints take the form of a tool tip window. When the refactoring menu is displayed, the content ofthe window provides you with the concise description of the currently selected option in the menu. As you move through the menu the old window will close and a new one will appear containing thedescription of the currently selected refactoring. Once the menu option is selected and the refactoringapplied, the big hint window closes automatically. You can see the big hint for Symbolic Rename refac-toring in Figure 3-4.

Figure 3-4

64

Part I: Introduction to Refactoring

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 64

Page 100: Professional Refactoring in Visual Basic

Shortcut Hints The purpose of a shortcut hints window is to list all available options and the keys to invoke them oncea certain refactoring has been selected. It is a floating window containing a two-column table. The leftcolumn contains the key and the right column describes the behavior that will result from pressing thatkey. The window can be moved, minimized, or closed. Shortcut hints appear for refactorings that havean interactive state and disappear automatically when you leave the interactive state (for example, bycommitting or cancelling your changes). Once you’re familiar with the shortcuts for a particular state,you can click the close button at the upper right of the shortcuts hint to suppress future appearances.Figure 3-5 demonstrates shortcut hints for the Extract Method refactoring.

Figure 3-5

MarkersWhen working on large files, you might need some help in getting around. For example, if you extract apiece of a method into a new method, you might wish to return to the place where the original methodis located. When performing the refactoring, Refactor! will automatically place a marker in the form of asmall triangle. Once refactoring has been committed, you can return to the starting point by pressing theEscape key. The caret will be moved back to the place where the marker was left, followed with the ani-mated circle to help you locate the caret. Once you return to the marker, it is collected and removed. Youcan have multiple markers, and you can move between them in the reverse of the order in which theywere created (stack-like). This functionality is similar to the bookmark functionality in Visual Studio, withthe difference that markers are created automatically as you perform certain refactorings. Figure 3-6shows a marker in place.

Figure 3-6

65

Chapter 3: Assembling a Refactoring Toolkit

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 65

Page 101: Professional Refactoring in Visual Basic

Linked IdentifiersThis feature permits you to simultaneously edit all the occurrences of an identifier. In certain type ofrefactorings it is possible to automatically identify all the instances of the identifier that needs to bechanged. This is the case with Rename Local refactoring, for example. Additionally, all linked items arehighlighted, and it is possible to navigate through them using the Tab key. In the context menu you canchoose to break the linkage. Once the edit is finished, you commit the refactoring by pressing the Enterkey, and items are delinked. You can see linked identifiers after the SSNValue field name has beenchanged in Figure 3-7.

Figure 3-7

Target PickersTarget pickers have the form of a horizontal line with an arrow. They permit you to select the location in thefile for the code resulting from active refactoring. You can move the picker by using the up- and down-arrowkeys. Once you commit the refactoring, newly generated code is placed in the location you selected with thepicker, and the picker disappears. Figure 3-8 shows a target picker.

Figure 3-8

66

Part I: Introduction to Refactoring

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 66

Page 102: Professional Refactoring in Visual Basic

The Replace Progress IndicatorCertain refactorings cannot be executed without some user interaction. In same cases, the programmerhas to decide what items refactoring should be applied to. Take an Introduce Constant refactoring, forexample: the tool can identify all the appearances of a certain literal value, but has no way of knowingwhich one of them you want to replace with a constant.

For example:

Private Function CalculateTax(ByVal amount As Decimal) As DecimaltxtTax.MaxLength = 20Return amount / 100 * 20

End Function

If you apply Introduce Constant refactoring on literal 20, Refactor! for VB will search the file for all otheroccurrences of this number. However, only you can decide which one should be replaced with a constant. Inthis case, one occurrence of the number is related to tax calculation and should be replaced with an appro-priate tax-calculation-related constant, while the other is concerned with a control property and should notbe replaced with the same constant, regardless of the fact that in this specific case those values coincided.

The tool uses the replace progress indicator to let the user pick items suitable for replacement during theoperation. In this mode you can cycle through all occurrences identified by Refactor! and apply a replace-ment as you go along. See Figure 3-9.

Figure 3-9

You have now seen how you can invoke and interact with Refactor! for VB, and you have seen all themajor visual and interface features. Next it’s time for you to see the refactorings this tool has to offer.

Quick Tour: Available Refactorings At the moment, Refactor! supports close to 30 refactorings. An additional four are available if you register at the Developer Express site (http://www.devexpress.com/vbrefactor), and they are certainly worththe trouble. Developer Express started out with the most basic refactorings, so only a few of the ones youwill see span more than one class. This list is not definitive; at Developer Express they continue to work onthe tool, and hopefully with time more refactorings will be released and supported by Refactor! for VB. InTable 3-1 you can see some of the key refactorings available at the moment (and also where in this bookthose refactorings are discussed).

67

Chapter 3: Assembling a Refactoring Toolkit

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 67

Page 103: Professional Refactoring in Visual Basic

Table 3-1: Refactorings Supported by Refactor! for VB and Covered in This Book

Refactoring Name Purpose Chapter

Encapsulate Field Turns a field into a property 11

Extract Interface Creates a new interface that defines all public mem-bers of a class and makes class implement this newlycreated interface

12

Extract Method Extracts a block of code into a new method 9

Extract Property Extracts a block of code into a new property 9

Inline Temp Replaces a temporary variable with an expression 10

Introduce Constant Replaces a literal value with a constant 9

Introduce Local Introduces a new explaining local variable

Make Explicit Adds As part to a variable declaration statement 5

Method to Property Converts a method declaration to a property

Move Declaration NearReference

Moves a declaration statement closer to the first useof the variable

10

Move Initialization toDeclaration

Moves initialization code to a declaration statement 10

Rename Local Renames all occurrences of a local variable 8

Replace Temp withQuery

Replaces a temporary variable with a method 10

Reverse Conditional Changes order of Then and Else blocks

Safe Rename Renames a method, while keeping the old name marked by the Obsolete attribute, and delegates thecall to a new method

8

Simplify Conditional Writes a condition in a simplified way

Split Initialization fromDeclaration

Splits the initialization code from the declarationstatement

14

Split Temporary Variable Introduces a new variable for an overburdened temporary variable

10

68

Part I: Introduction to Refactoring

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 68

Page 104: Professional Refactoring in Visual Basic

I will explain in detail each refactoring that Refactor! supports throughout the book; the table shows thechapter where each refactoring has been applied and demonstrated. You will also notice that the list ofrefactorings that Refactor! supports is a lot smaller than the list of refactorings I deal with in the book.Not all refactorings are easy to automate, and as I already mentioned, work on Refactor! is continuouslyin progress. I hope that in time the two lists will start to converge, and the number of automated refac-torings will close upon the list of refactorings I deal with in this book.

Refactor! for VB is a scaled-down version of Refactor! Pro, a commercial tool. Some of the hiddenoptions in the Pro version can be accessed with a free version with a little bit of tweaking. Take a look atAppendix A of this book to see how to activate hidden options in Refactor! for VB.

That concludes this quick look at refactoring tools and Refactor! for VB specifically. Now it’s time to takea look at another very popular technique (one close in philosophy to refactoring) and another importanttool in your refactoring toolkit — unit testing and NUnit.

Unit-Testing Basics: The Testing HarnessRefactoring can be addictive. Once you start, it’s difficult to stop! However, one step too far, and youwon’t be able to find your way back. Make one simple mistake, and suddenly the program is not doingwhat it was supposed to. You try reversing the latest changes but to no avail. You made a mistake some-where along the way, and you just can’t figure out where. The only solution is to go all the way back tothe beginning. The scenario I just described is not so uncommon for a novice to refactoring. Refactoringhas to be performed in a disciplined manner, but that alone will often not suffice.

Refactoring has to be performed carefully, step by step, and each step has to be followed by verification.You have to make sure your changes do not influence the behavior of your program. Remember the defini-tion of refactoring? You are improving the design without changing the behavior. So how can you verifythe behavior in a simple yet efficient way? The solution is unit testing.

As some of you might already know, the Visual Studio Team System comes with integrated unit-testingsupport. At this point you might ask yourself why I chose to talk about NUnit instead. First of all, TeamSystem is the top of the line of Visual Studio products and that comes at a price. If I had used it, this sec-tion would have had a smaller audience. Secondly, NUnit is more applicable to a test-driven developmentstyle (something a lot of you might decide to pursue). At any rate, exposure to NUnit is also a good start-ing point for unit testing with Visual Studio. Because NUnit is less integrated with Visual Studio and lessGUI-based than Microsoft’s tool, by pursing unit testing with NUnit you will acquire a generic and moreuniversal understanding of the unit-testing process. On a side note, there are other alternatives to unittesting in VB. For example, two other accomplished open-source unit-testing frameworks are MbUnit(http://www.mbunit.com) and csUnit (http://www.csunit.org).

In this section you will learn more about unit testing in general and the benefits it brings. You’ll explore inmore detail one open-source unit-testing framework, NUnit. You will learn how to install it and use it andhow to write unit tests with its help of NUnit. Finally, I’ll take you back to the Calories Calculator applica-tion and write tests for some of the classes in the application so you can see unit testing in practice. But Iwant to start by taking a look at how unit testing as a discipline came about.

69

Chapter 3: Assembling a Refactoring Toolkit

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 69

Page 105: Professional Refactoring in Visual Basic

Why a Unit-Testing Framework?One of the ways you deal with software complexity is through testing. Testing is your attempt to discoverbugs and errors in the software you are creating before it reaches a final user. In an attempt to ensure soft-ware quality, companies often have teams, even departments, dedicated to different forms of testing. Thetypes of tests these teams perform vary and can be geared toward ensuring different qualities of the soft-ware. They might perform functional, usability, integration, load, stress, and other types of tests. All ofthese are important parts of the software-development process.

Manual Data Entry In order to perform thoroughly any type of test, a lot of time and resources are needed. It might takedays, even weeks, before results are obtained and feedback ends up in the hands of a programmer. But it is of utmost importance that the coder be able to test the code every step of the way during thedevelopment process.

Each time the most minimum functionality is added, changed, or removed, programmers have a natu-ral tendency to want to assure themselves that the software still works correctly and that no fresh bugshave been introduced. The most obvious way to go about this is for the programmers themselves tosimulate users. An IDE makes it easy to start the execution, enter the desired data, and verify the result.In case anything unexpected occurs, the process can be repeated and followed through with a debug-ger, enabling you to observe the execution depicted in the source code, line by line, and to interactivelyreview and change variable data states. This makes the task of identifying the offending bug a lot easier.At this point, you might remember that this is exactly the approach I used to test the CaloriesCalculator application in Chapter 2.

With all these capabilities and tools, programmers still produce very costly and difficult-to-identifybugs. Why does this happen? Partly because the type of ad hoc testing I just described, performed byprogrammers manually, has some very serious drawbacks:

❑ It requires a lot of manual data input, making the whole process very tedious. (This drawback isdirectly related to the next one.)

❑ Because it is tedious and time-consuming, coders limit their testing exploits to scenarios mostdirectly related to their current tasks. However, there is no guarantee that only the tested func-tionality has been affected by the current changes.

❑ For the same reason, the pool of test data is often insufficient, failing to test for invalid and limit values.

❑ Because they are not automated, the tests are difficult to repeat in a reliable manner. In onesweep you might test for some important values and in the next those values are forgotten or misspelled.

❑ The cost in time associated with this type of testing means that the frequency at which tests areexecuted is low, leading to a situation in which a lot of changes have been introduced withouttest assurance in between. If the bug is identified after substantial changes have been applied, itis a lot more difficult to locate it. This leads to a vicious circle wherein more and more time islost because of an unreliable development process, while the lack of time discourages a system-atic approach to testing.

70

Part I: Introduction to Refactoring

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 70

Page 106: Professional Refactoring in Visual Basic

A lot of programmers identified the drawbacks I just mentioned and reached the following conclusion:automating the tests would greatly improve their quality and would make them less costly and easier toperform. So what can we do about it?

A First Attempt at Automated Unit Testing: Ad Hoc Unit TestingThe easiest and the most direct way to test a piece of code in an automated and repeatable manner is to writea program to exercise the targeted code. For example, if you write a dynamic link library, you can program anexecutable that will consume the functionality offered by the dll and make sure that dll behaves according tothe specification.

While this approach to automating testing is a great improvement compared to manual testing, a numberof problems still become apparent once you adopt it:

❑ It gives you no way to write tests in a systematic and standardized way, making it difficult foryou to maintain the quality of tests and promote their team-wide adoption. There are no realguidelines for programming the tests.

❑ If you do not perform the testing in a focused way, by isolating one piece of code from the restof the application state, it may still be possible to discover the bug, but it can be very difficult topin down the offending line of code. This approach to test automation is similar to the recordingof user interaction: it is based on a surface view of tested code.

❑ In certain setups, you might want unit testing to play an integral part in a wider developmentprocess. This would require better tools integration, standardized output, reports, and the like.

No wonder programmers tried to find the solution to all these problems. Programmers needed the ability towrite their own tests, counting on detailed knowledge of tested code. These tests would have to be executedand repeated at will. They would have to exercise small modules of code in isolation so no time would belost in search for the source of the bug. Finally, the solution needed to encompass standardized means ofcapturing and displaying results.

Viola! Unit-Testing FrameworksAll of this led to the appearance of unit-testing frameworks, which granted programmers new levels offreedom and productivity. While solving many of the previously mentioned problems, the tests writtenin this manner had another very interesting side effect.

It became apparent that these tests could serve as a very good tool for expressing user requirements in anexplicit, unambiguous manner. Diagrams, storyboards, use cases, and the rest of the traditional require-ments-taking tools are all subject to human interpretation. Because unit tests are written in code and thenexecuted as a program, it is the computer that decides whether a requirement has been satisfied — that is,whether the test has failed or not. Mind you, it is still programmers who have to write the right test, oneembodying the right requirement.

This style simplifies the development in another way — it eliminates the constant need to decide what exactly needs to be implemented, what methods and classes are actually needed. Programmersoften speculate about scope of the program: “We might need this feature in the future” or “What if thecustomer needs to solve this problem later on?” If you actually write each test before you implementthe code, this dilemma is no more. You implement only the code that makes all tests execute success-fully, and nothing more. If you don’t have the test that uses it, it means you don’t need the code. Thisapproach to programming is test-driven development.

71

Chapter 3: Assembling a Refactoring Toolkit

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 71

Page 107: Professional Refactoring in Visual Basic

Next I am going to help you examine a popular unit-testing framework in more detail. This frameworkis arguably one of the most popular in the .NET world. However, I am just going to scratch the surface.As a matter of fact, there are already books dedicated to unit-testing test-driven development in .NETexclusively, so you can look at the following paragraphs as an encouragement and an invitation to dipinto this practice further.

Your First Taste of NUnitNUnit framework is an open-source unit-testing framework for .NET. Thanks to the language neutrality of.NET programming languages, NUnit can be as easily used in Visual Basic as in C#, managed C++, or anyother .NET language. NUnit was developed as a port of the JUnit framework, written by Erich Gamma andKent Beck for unit testing in Java. JUnit sparked a wave of tools and frameworks that resulted in the portingof JUnit to a variety of languages, and in the development of various types of extensions that facilitate test-ing in diverse environments: Web applications, mock-object development (see the “NMock Object-MockingFramework” section later on in this chapter), GUI testing tools, and others. The sheer size of the responsethis framework provoked in the developer community is a testimony to its success and usefulness.

As with any framework, NUnit provides a lot of features that can be reused. This streamlines the testimplementation and its execution. When you write tests with NUnit, you program plain old VB classes.The first version of NUnit required you to extend (inherit) classes provided within the framework, butsince version 2.0, an approach more in line with .NET programming style has been adopted. You need toapply different attributes provided by NUnit to the classes and methods, so that NUnit knows that thesecontain unit tests. When you use NUnit to run your tests, it searches for classes and methods markedwith the NUnit attributes to execute them and report the results.

Now, before you get into the details of implementing tests with NUnit, I want to dedicate a few lines toinstallation procedures.

Installing NUnitNUnit is available for free from the NUnit site, www.nunit.com. NUnit is distributed under an open-sourcelicense and installation comes in different flavors, with the source code available for download in addition tothe standard windows installation package. Since work on NUnit is permanent, you should always down-load the most recent stable version. On the web site, this version is generally marked as “Recommended.”

After the installation, NUnit will place an item in your Start menu. One of the menu subitems is calledNUnit-GUI and is used to activate the NUnit graphical interface. The NUnit GUI is used to run tests: it’sone of NUnit’s test runners. There is also a console test runner available, which is recommended for moreadvanced and integrated build setups. As a first step after the installation, I recommend you open theNUnit GUI and execute the sample tests provided with the NUnit installation. By running these samples,you can achieve two objectives:

❑ You verify that installation of NUnit went smoothly. One possible problem you might face is the version of .NET framework you have installed. If you have Visual Studio 2005, .NETFramework 2.0 comes with it by default and should work with the latest version of NUnit available at the moment of this writing, 2.4.5.

❑ You familiarize yourself with NUnit’s graphical user interface. While there are other ways torun NUnit, this is the most common place to start.

72

Part I: Introduction to Refactoring

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 72

Page 108: Professional Refactoring in Visual Basic

You are now going to take a look at the GUI after the successful installation of NUnit. In order to getthings going you can try running some of the samples that come with it. VB samples are placed in theSamples/VB folder inside the NUnit installation folder. These tests are distributed as source code, soyou should build the samples first with the Visual Studio IDE. Don’t worry about the code for themoment; you will get to that later. For now, just build the dll. After that, using the File ➪ Open optionin the NUnit GUI, locate the dll you just created and hit the Run button. Figure 3-10 shows the NUnitGUI after the execution of a sample VB project.

Figure 3-10

On the left side of the window you can see a tree view representing all available tests. Once you get to writing tests you will see that nodes represent a hierarchical organization of assembly, namespaces,classes, and methods and properties in your test project. By selecting a certain node in the tree, you canchoose to run only a selected test or group of tests. This is a useful feature once the number of testsgrows such that it would take too long to execute all the tests each step of the way.

On the right-hand side are two buttons. You can use the Run button to start and the Stop button to inter-rupt the execution of tests at any time. Below the buttons is the progress bar, which indicates the state oftest execution.

Under the area with the Run and Stop buttons and the progress bar you can see a set of tabs that displaydifferent information related to test execution.

❑ Errors and Failures lists all the tests where an error was encountered.

❑ Tests Not Run lists all tests that didn’t get executed. These can be tests that are marked with theIgnore attribute. If a test is marked with this attribute, it will not be taken into account by the testrunner — for example, if you didn’t finish this particular test yet and you’d rather skip it. (I’ll getinto the details of attribute use with NUnit once you start implementing your own tests.)

73

Chapter 3: Assembling a Refactoring Toolkit

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 73

Page 109: Professional Refactoring in Visual Basic

❑ Console.Out displays console output from the program. Generally, this is the debug informa-tion left in the code by Console.WriteLine.

❑ Console.Error displays all unhandled exceptions encountered during the test execution.

❑ Trace displays trace output written by your tests using the Trace class from the System.Diagnostics namespace.

❑ Log displays NUnit internal log messages. NUnit uses the log4net logging framework as aninternal logging mechanism.

Finally, in the area below the tabs, a detailed location of each error or failure is displayed.

Color plays an important part in communicating the success or failure of test execution. Nodes in thetree are displayed in different colors depending on the execution’s success (or lack of it). Table 3-2 showsthe meaning of each color.

Table 3-2: NUnit Color Legend

The progress bar will be rendered in a similar way. There is one difference, however: it is enough for asingle test to fail for the progress bar to turn red.

Now it’s time for you to take a look at the code and write your first test.

Implementing Your First TestIn order to implement your first test, you’ll go back for a moment to the application I refactored inChapter 2, the Calories Calculator. As you might remember, the core logic ended up in three classes:Patient, MalePatient, and FemalePatient. You’ll use Patient and FemalePatient classes forthe NUnit demo. You will write NUnit tests that can verify the behavior of these two classes. After youimplement the tests, each time you modify one of these classes — to change or add functionality, toresolve a bug, or to refactor it — you will be able to check that no undesired effect has been producedby the changes and that the code still behaves as intended.

Creating the Test ProjectYou need to start out by creating a new project for your tests. This is because you do not want to distributeany of the tests with the production code. You’ll use the existing Class Library Visual Studio template tocreate your new project. That means you’ll end up with a dynamic link library once you build the project.You have already seen how you can use the NUnit GUI application to execute tests in such a library. Youcan see the Visual Studio New Project dialog window in Figure 3-11.

Color Meaning

Green Success

Yellow Ignored

Red Failure

Gray Test not executed yet

74

Part I: Introduction to Refactoring

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 74

Page 110: Professional Refactoring in Visual Basic

Figure 3-11

Now you need to add a reference to the NUnit dynamic link library to our project. The name of the dll isnunit.framework, and you can locate it on the .NET tab in the Add Reference dialog. This dialog win-dow is shown in Figure 3-12, and you can open it by selecting the Project ➪ Add Reference option.

Figure 3-12

75

Chapter 3: Assembling a Refactoring Toolkit

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 75

Page 111: Professional Refactoring in Visual Basic

Add the existing Patient, MalePatient, and FemalePatient classes to the project by using Project ➪

Add ➪ Existing Item. Once the Add Existing Item window opens, you need to locate .vb source files con-taining the classes you are going to test. The Add Existing Item dialog is shown in Figure 3-13.

Figure 3-13

You need to repeat this step for each class, because each resides in its own .vb file.

Now that you have access to all the classes you are going to test, it’s time to add a first test class to the project.

Creating a Test FixtureAs I already mentioned, writing unit tests is akin to any other type of programming. As always, the firststep is adding a new class to the project. Call it TestFemalePatient. This class needs to be marked with a NUnit attribute: TestFixture. By marking the class with the TestFixture attribute, you are telling the NUnit test runner that the class contains tests that need to be executed. You can group different tests in the same class if they share runtime resources. This often means that you’ll end up having oneTestFixture per tested class.

Take a quick look at the class declaration:

Imports NUnit.Framework

<TestFixture()> Public Class TestFemalePatient

End Class

76

Part I: Introduction to Refactoring

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 76

Page 112: Professional Refactoring in Visual Basic

You can see how the NUnit.Framework namespace is imported and how the TestFixture attributeis applied to the TestFemalePatient class. If you build the project now and open the dll using theNUnit GUI, you will see the class in the test tree view. You can even run the tests. Because the class isstill empty, the progress bar and test tree are colored yellow: the test runner wasn’t able to find anytest methods yet.

Next it is time to implement your first test method.

Writing TestsYou have now prepared the first class that will become part of your test suite. In NUnit, each test is writ-ten in the form of a method. This method has to be public. It has no parameters and does not return anyvalues. In order to be identified as a test method to be executed by the test runner, it has to be markedwith another NUnit attribute: Test. Here you are going to add such a method to your class, and you aregoing to call it IdealBodyWeight. It means you are going to test the IdealBodyWeight functionalityoffered by the FemalePatient class. In this particular case you are going to test the method for the mostcommon situation, wherein valid parameters are provided for the calculation. (Just to remind you, theformula provided for ideal weight calculation is not suitable for persons shorter than five feet.)

The mechanics for writing tests are the following:

❑ Prepare all parameters and property values the object needs for execution.

❑ Create an instance of the tested class and set all the necessary properties of the instance.

❑ Execute the tested method with parameters that were already created.

❑ Compare the effect or value returned by the tested method to an expected value or effect.

In the case of FemalePatient, you need to set the Age, Height, and Weight properties of theFemalePatient instance. Then you need to call the IdealBodyWeight method of an object. Thismethod expects no parameters, but it does return the Decimal type value. As a last step, you will tellNUnit to assume that the expected result and real result are equals. If they are not equal, NUnit willreport the error in test execution. Listing 3-1 shows the code for the process just described.

Listing 3-1: Testing the IdealBodyWeight Method

‘NUnit Test attribute applied to a test method<Test()> Public Sub IdealBodyWeight()

‘Instance of object under test created and properties set Dim femalePatient As FemalePatient _ = New FemalePatient femalePatient.HeightInInches = 72 femalePatient.WeightInPounds = 110 femalePatient.Age = 30

Dim expectedResult As Decimal = 161.15626

Dim realResult As Decimal realResult = femalePatient.IdealBodyWeight ‘Result of test defined through NUnit assertion‘by comparing that expected and returned result are equalAssert.AreEqual(expectedResult, realResult)

End Sub

77

Chapter 3: Assembling a Refactoring Toolkit

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 77

Page 113: Professional Refactoring in Visual Basic

This code listing should not raise any eyebrows. Except maybe for the last line, where the Assert classof the NUnit framework is used. The next section takes a look at it in detail, since it is the crucial line inthe listing.

Using Asserts NUnit includes a class, Assert, that provides a number of shared methods. These methods are used to communicate to NUnit how you expect the program to behave. By asserting, you are making anassumption or claim about something. This assumption can prove to be true, and in that case a greenprogress bar is displayed. Or it can fail, resulting in a red node and progress bar in the NUnit GUI.

In this specific case, here is what I did:

1. I used a formula for the ideal weight calculation to obtain the expected value.

2. I wrote down this literal value in the test code by assigning it to a local variable calledexpectedResult.

3. I used an instance of FemalePatient to obtain the result of a calculation performed in the program and stored it in local variable realResult.

4. I established that these two values are equal.

Now it’s time to run NUnit again. Since the code is behaving as expected, you should see green in theNUnit GUI.

The Assert class contains a number of methods. They all do more or less similar things. If you thinkabout it, you only need one method to verify about any type of assertion you might think of — a methodto check if a certain Boolean statement is true. Unsurprisingly, there is exactly such a method in theAssert class. It is called IsTrue.

Another method, similar to AreEqual, is AreSame. This method is used to assert that two references arepointing to the same instance. The following code snippet illustrates its use:

Dim femalePatient1 As FemalePatient = New FemalePatientDim femalePatient2 As FemalePatient = New FemalePatientDim femalePatient3 As FemalePatient = femalePatient1

Assert.AreSame(femalePatient1, femalePatient2)Assert.AreSame(femalePatient1, femalePatient3)

Have you guessed already? In this case the first assert will fail, while the second will pass. I have initial-ized femalePatient3 with a reference to femalePatient1. Both variables point to the same object,and ultimately to the same memory space. This is why the second assert passes the test. In the firstassert, I am comparing two completely different objects. This results in the AreSame method returning avalue of False, making the first assert fail the test.

If you want more information about the results of the Assert.AreSame test shown in the code illus-tration, take a look at Chapter 11, where I talk in more detail about object identity and the differencesbetween value and reference types.

Yet another method available in the Assert class is the method IsNull. This method lets you assert thata certain variable does not refer to any instance.

78

Part I: Introduction to Refactoring

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 78

Page 114: Professional Refactoring in Visual Basic

A number of other methods are added to the class for your convenience. A lot of them are negative forms ofmethods you have already seen — for example, AreNotEqual, AreNotSame, IsFalse, IsNotNull, and soon. It’s worth exploring the available methods in the Assert class further yourself. For now you need tocontinue writing tests for the FemalePatient class.

We need to test another method in our application. This method is called DailyCaloriesRecommended.To test it, you can repeat the steps you took to test IdealBodyWeight. The method I came up with isavailable in Listing 3-2.

Listing 3-2: Testing the DailyCaloriesRecommended Method

<Test()> Public Sub DailyCaloriesRecommended()Dim femalePatient As FemalePatient _= New FemalePatientfemalePatient.Height = 72femalePatient.Weight = 110femalePatient.Age = 30

Dim expectedResult As Decimal = 1015.2

Dim realResult As DecimalrealResult = _femalePatient.DailyCaloriesRecommended()

Assert.AreEqual(expectedResult, realResult)End Sub

You can now run the NUnit GUI again and see both of the methods pass. It means that the second testmethod was written well also.

Still, there is one detail that is not to my liking. Both methods have large portions of code that are completelyidentical. You are better off writing that code in only one place. The next section shows how you can.

Using SetUp and TearDownIn the NUnit framework it is possible to mark a method with a Setup attribute. This will signal to NUnitthat this method should be executed before any of the test methods are run. You can use this attribute tomark a method that can contain code common to both test methods, and to create objects that will repre-sent the test fixture.

In order to do this you need to perform two modifications to the code:

1. Add a new method and mark it with the SetUp attribute. The method creates an instance of theFemalePatient class and assigns valid values to its properties. The code you need is already con-tained in both test methods, so you can move it to the new CreateFemalePatientInstancemethod.

2. Promote the femalePatient local variable to an instance variable.

Remember, TestFixture is just another plain old VB class, so you can add instance variables and methodsat will. It’s time to take a look at the test class. TestFemalePatient is shown in Listing 3-3.

79

Chapter 3: Assembling a Refactoring Toolkit

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 79

Page 115: Professional Refactoring in Visual Basic

Listing 3-3: An Example of SetUp Usage

Imports NUnit.Framework

<TestFixture()> Public Class TestFemalePatientPrivate femalePatient As FemalePatient

<Test()> Public Sub IdealBodyWeight()Dim expectedResult As Decimal = 161.15626Dim realResult As DecimalrealResult = femalePatient.IdealBodyWeightAssert.AreEqual(expectedResult, realResult)

End Sub

<Test()> Public Sub DailyCaloriesRecommended()Dim expectedResult As Decimal = 1325.4Dim realResult As DecimalrealResult = _femalePatient.DailyCaloriesRecommended()Assert.AreEqual(expectedResult, realResult)

End Sub‘SetUp attribute used to mark a method<SetUp()> Public Sub CreateFemalePatientInstance()

femalePatient = New FemalePatientfemalePatient.HeightInInches = 72femalePatient.WeightInPounds = 110femalePatient.Age = 30

End SubEnd Class

At this point you are probably thinking that the last step is quite familiar, similar to some transforma-tions you saw in Chapter 2.

You’ve guessed it. When you created the CreateFemalePatientInstance method you refactored theTestFemalePatientClass. You extracted one method and promoted one local variable to an instancevariable. Unit tests are an integral part of your code base, so you need to maintain them in optimalshape. Because of this, they are also subjected to refactoring.

One of biggest objections to unit testing is that it makes you write a lot of code that never gets to beexploited in production. It’s just another artifact that you use to better your procedures and to protectyourself from your own mistakes. While this might be true, the benefits of unit testing greatly outweighany drawbacks. However, in order to keep those shortcomings to a minimum, it is very important tomaintain and refactor your test code.

Going back to the SetUp method, you have seen that it gets executed before each method marked withthe Test attribute is called. Following simple logic, you might ask for a way to write a method thatgets executed after each test method is performed. NUnit provides for such a case. If a method is to beexecuted after each test method, it should be marked by the TearDown attribute.

Similar to the SetUp and TearDown attributes are the SetUpFixture and TearDownFixture attributes. Ifyou mark a method with SetUpFixture, this method gets executed once the test class or fixture is created.A method marked with TearDownFixture is the last method that will get executed in the test class, afterthe execution of all test methods.

80

Part I: Introduction to Refactoring

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 80

Page 116: Professional Refactoring in Visual Basic

Dealing with Exceptions: the ExpectedException AttributeAs I mentioned earlier in this chapter, you have now tested a few methods of the class in the most commonsituations. However, you have seen that some of the formulas are not applicable to people whose height isless than five feet. If someone tries to use the class to calculate ideal body weight for such a person, youwould expect it to throw an exception.

Testing your code for boundary and exceptional circumstances is a very important part of your testingmethodology. You cannot write tests for every possible value, so you need to try to exercise the code for themost sensitive values. These are generally boundary and invalid values. Experience teaches that bugs areespecially common in such situations. Programmers often fail to take into account less common values.

In the case of the Patient class, the most logical place to control for invalid height value is the Heightproperty of the Patient class. Since FemalePatient inherits Patient, for demonstration purposes youcan use the same FemalePatient instance to test whether an exception is thrown.

The way to tell NUnit that you expect an exception to happen is to mark the test method with anotherattribute, ExpectedException, in addition to Test. The ExpectedException attribute needs a singleparameter, a type name of the exception you expect. What happens once the test runner executes themethod marked with ExpectedException is a check that the exception is thrown. If the exception isthrown, then everything went as expected, and the green light is shown in the NUnit GUI. However, incase no exception is thrown or the exception thrown is of a different type from the one specified in theExpectedException attribute-line creation, the test has failed, and the signal shows red.

You can expect an ArgumentOutOfRangeException to be thrown if you try to assign a value of lessthan 5 to the Height property:

<Test(), ExpectedException(GetType( _ArgumentOutOfRangeException))> _Public Sub HeightLessThan5Ft()

femalePatient.HeightInInches = 59End Sub

After adding this method, I execute the tests again. However, the HeightLessThan5Ft test has failed. I now take a look at the Get part of the Height property in the Patient class, and sure enough, I forgotto limit the value of height in code. I now have to modify the Height property in the Patient class. Itends up looking like this:

Public Property Height() As DecimalGet

Return heightInInchesValueEnd GetSet(ByVal Value As Decimal)

If Value <= 60 ThenThrow New System.ArgumentOutOfRangeException( _“Height has to be greater than five feet (60 inches)“)

End IfheightInInchesValue= Value

End SetEnd Property

I hit the Run button in NUnit again, and everything’s green.

81

Chapter 3: Assembling a Refactoring Toolkit

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 81

Page 117: Professional Refactoring in Visual Basic

This sums up the basic usage of NUnit, but we have a few more important details regarding unit testingto cover before you’re done with this chapter.

The Test-Driven ApproachThe simple test class illustrates the basic concepts behind unit testing and the NUnit framework well.You used familiar code and provided a testing harness that can verify its correctness. However, this is arather rudimentary approach to unit testing.

With the wider acceptance of unit testing by developers, a very important change in the “chicken oregg” dilemma in software testing occurred. Programmers note that they often lack explicit means todefine requirements. Requirements are often ambiguous, expressed in text in the form of storyboards,diagrams, and so on. Even more often, in an attempt to produce more complete solutions that can alsoresolve any future requirements, programmers go to great lengths in producing code that finally nevergets used. This is a great loss of time and resources.

Unit tests are quite useful in resolving these problems. There isn’t too much competition when it comesto explicitly defining the behavior of your code. The code itself is as explicit as it gets. Also, if you writethe test, it must be because there is a requirement for your code to behave in a certain way. You will notwaste your time testing the code for behavior that is not required.

Wouldn’t be nice if you could have tests for your code even before you wrote it? All you would need todo is program the classes in such a way that none of the test fails, and you would be done. If there is notest for certain functionality, it must be because that functionality is not really needed.

In test-driven development, you actually start out by writing tests first. Since no code exists to imple-ment the tested functionality at this point, the test will obviously fail on the first run. You can then writecode to make the first test execute without error. You continue by adding the next test and then the codeto make that test pass, and so on. Being guided by requirements embodied explicitly in test code, youmaintain your code lean, relevant, and clear of bugs.

So, how does this work out in practice? Go back to the Calories Calculator application for the moment.

Imagine there is another method you would like to add to the FemalePatient class. This method iscalled BodyFatContent, and all needed data is already available in the FemalePatient class in theform of properties. You are going to start by adding a Test method to the TestFemalePatient class.Call it BodyFatContent, and mark it with the Test attribute. In the body of the method, you are goingto call the BodyFatContent method on the femalePatient instance. Since this method still does notexist in the FemalePatient class, Visual Studio reports an error.

In C#, Visual Studio 2008 has a Smart Tag feature that can generate a new method stub in theFemalePatient class automatically, saving you a few keyboard strokes. It’s a nice feature and help-ful for writing tests in test-driven way. Unfortunately, this feature is not present in Visual Basic.

Take a look at the code you have so far:

<Test()> Public Sub BodyFatContent()Dim expectedResult As Decimal = 36Dim realResult As Decimal = _

82

Part I: Introduction to Refactoring

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 82

Page 118: Professional Refactoring in Visual Basic

femalePatient.BodyFatContent()Assert.AreEqual(expectedResult, realResult)

End Sub

Now you can go back to the FemalePatient class and implement the BodyFatContent method. Youcan run the tests again and check that all have passed.

Test-driven development is an important practice in agile teams. It goes hand in hand with refactoring as a means to achieve new levels of productivity and software quality. There is much more to test-drivendevelopment than what I can cover here, but hopefully I have managed to at least awaken your interest.

Other Test Tools to Consider There are a plethora of other tools that you might find useful when writing unit tests. However, I don’thave space to cover all these tools exhaustively in this chapter. Instead, I’ve chosen a handful of toolsthat I have found complement my refactoring efforts quite nicely.

The TestDriven.NET Visual Studio Add-InYou can get a much better integration between NUnit and Visual Studio if you install the TestDriven.NETopen-source Visual Studio add-in. It will permit you to run and stop tests directly from Visual Studio. Also,it will display the test results inside the Visual Studio window. This way you do not need to switch betweenthe NUnit test runner and Visual Studio while you program.

You can download this add-in from www.testdriven.net. Take note that the free personal license isintended for trial users, students, and open-source developers.

The NUnitForms GUI-Testing FrameworkWhen testing parts of the GUI, you need an approach that’s a bit different from the one you take in test-ing plain old VB classes. You need to simulate user interaction with the program. This is not so easy todo with NUnit. NUnitForms is an extension of NUnit that was made to facilitate the task of testing GUIsmade with Windows Forms. It can clean forms, detect and manipulate dialog windows, and so on.

While NUnitForms can help you in your task of creating standard unit tests, this framework can also behelpful in producing another type of test. NUnitForms can be used to simulate user interaction or even torecord it and replay it later. If you run these tests not against a single GUI class, but against the completeapplication, you can create a sort of integration of acceptance tests.

I found this approach very useful when faced with a completed application that comes without any unittests. You cannot always dedicate the time necessary to introduce unit tests a posteriori. However, in theprocess of maintenance you may need to refactor the application, and in those cases this type of test is the next best thing. NUnitForms can be downloaded from SourceForge.net at http://nunitforms.sourceforge.net.

The NMock Object-Mocking FrameworkAs I already mentioned, when you write unit tests you need to test objects in isolation. This is a veryimportant feature of unit testing because it can help you test exactly the desired functionality and pindown a failure to an exact method or property. However, not all the code you need to test is as simple

83

Chapter 3: Assembling a Refactoring Toolkit

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 83

Page 119: Professional Refactoring in Visual Basic

as the example you have seen in this chapter. Your code might use some very complex classes anddepend on them to behave properly. This makes writing unit tests much more difficult to accomplish.

Mock objects to the rescue! Instead of using real objects, you can use substitute objects that take on theinterface of a real object. This substitute, a mock object, can verify the way the object being tested is inter-acting with it. If the interaction is not as expected, the test will fail. Also, a mock object can provide theobject being tested with expected return values.

For more information on the subject and for an open-source mock-objects framework, check outhttp://nmock.truemesh.com/index.html.

The NCover Test Coverage ToolYou should always attempt to write your tests as thoroughly and with as broad a focus as possible.During the execution of the complete suite of unit tests for your application, you should strive to getevery single line of your code exercised. However, you cannot always be as systematic as you would liketo be. So instead of leaving the quality of your tests in human hands, you can use a tool that reports indetail what lines of your code were executed and what lines you failed to test. Such a tool is available athttp://ncover.org. NCover can be a great help in completing your suite of tests with all relevant cases.

A Few Words about Version ControlWhile unit testing is not so widespread, source-code version control is today universally accepted. Idon’t know a team or company without some type of version control installed. This is why I will notdedicate much time to the subject, except to discuss the way it relates to refactoring.

From the refactoring perspective, the importance of version-control systems is two-fold.

❑ First of all, they grant you another level of protection. No matter how disciplined you are inrefactoring gradually, applying small changes and then verifying correctness with test execution,sooner or later you are bound to go one step too far. An error is introduced, and it’s just too costlyto debug your way out of it. In that case, you can count on version control to provide a backup.So, in the worst-case scenario, you can start all over again — fortunately, not from the beginning,but from the last snapshot of the code you took. If you version code regularly, such a setback willprobably not be catastrophic.

❑ Another way in which version-control systems affect refactoring has to do with code ownership.A lot of teams, as the first step out of the primordial chaos of team development, choose to imple-ment individual code ownership. In this model, programmers have their own classes, components,or other code units that belong only to them: no other programmer is authorized to change them.Some version-control systems, namely SourceSafe, promote this model by default. When one pro-grammer checks out a file, no other programmer is allowed to edit it. The idea behind this formof team organization is to promote order and field expertise. Programmers can become experts intheir own area, thus being more efficient and productive. They can be sure that nobody will med-dle with their code, which gives them a feeling of control and confidence.

For new and inexperienced teams this form of concurrent version control is definitely a step for-ward. However, it also has various drawbacks that become more apparent as refactoring practicesare adopted. With focused expertise, the phenomenon of programmer-owners can appear. The

84

Part I: Introduction to Refactoring

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 84

Page 120: Professional Refactoring in Visual Basic

code is written in such a manner that it is very difficult for other programmers to take over. Thisleads to the proliferation of diverse, often cryptic, coding styles and standards. Because you areallowed to change only your own code, this model can cause stagnation. And then, it can playdirectly against refactoring.

Some refactoring techniques require widespread changes to source code. These changes are notlimited to a single method or a class. In this case, individual code ownership can work againstyou. For example, how can you rename a class if you are not allowed to change all the code thatis making use of that class? For that reason, agile teams tend to adopt collective code ownership. Inthis model, any member of a team is allowed to modify any project item. Also, it is possible formore than one programmer to work on a single file simultaneously. When this happens, if con-flicting changes are discovered at the moment of check-in, these are resolved by merger.

SummaryEffective tools are very important for the successful adoption of refactoring practices. These tools cangreatly improve your productivity. The most important tool from a refactoring perspective is one thatyou can use to automate the application of refactoring transformations.

With Visual Studio 2005, Visual Basic programmers have such a tool available to them free of charge.Refactor! for VB from Developer Express integrates seamlessly with Visual Studio 2005 using VisualStudio add-in architecture. You have now taken a look at installing and using this add-in. Refactor! hasa number of novel GUI widgets that help you interact with the user in an unobtrusive way. You havealso seen how to activate each of the more than 20 refactorings this tool provides.

Another programming practice that almost always goes hand in hand with refactoring is unit testing. Byunit testing your code, you can easily verify its correctness after any modification is applied to the code base.You have seen in action an open-source unit-testing framework that can help you streamline test creationand execution. This framework is called NUnit. You used familiar code from the sample application in theprevious chapter and provided it with unit tests. You also saw how to install and run NUnit.

There are other open-source tools that can help you in your refactoring tasks. I have mentioned some ofthem. I also mentioned a very popular practice among agile teams often referred to as test-driven develop-ment. Finally, I reserved a few lines for source-code version control and its role in the refactoring process.These tools can be used to promote some form of code ownership, and with agile teams the preferred formis collective code ownership.

After this chapter, you are armed with all the necessary tools for your refactoring process. In the nextchapter you start to deal with some of the basic refactoring techniques very relevant to VB programmers.

85

Chapter 3: Assembling a Refactoring Toolkit

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 85

Page 121: Professional Refactoring in Visual Basic

79796c03.qxd:WroxPro 2/22/08 4:58 PM Page 86

Page 122: Professional Refactoring in Visual Basic

Rent-a-Wheels Application Prototype

Refactoring is a very practical, hands-on type of art. Therefore, it is difficult to talk about themechanics, motivation, and benefits of refactoring unless there is a good illustration to accom-pany it. While small snippets of code serve well to prove a specific point, you might be leftwanting a look at the bigger picture, or an example of of refactoring in a real-life situation.

In order to remedy this problem, I have decided to present you with a real-world scenario. I will goon from here to implement the complete and fully functional application. I will apply refactoring as I go and explain the logic behind the refactoring process in detail. That way, not only will each refac-toring technique be illustrated with code samples, but you will see the refactoring process appliedthroughout the lifetime of this sample application.

I will end each chapter in the book with a demonstration of refactoring in practice. I will use theRent-a-Wheels application for this purpose. Throughout the book you will witness the progress of the application from a simple but fully functional GUI-based prototype to a full-blown tieredenterprise application.

Since I will refer to the Rent-a-Wheels application in almost every chapter that is to follow, youshould try to analyze the application while reading this chapter and think of possible weaknessesin the design and implementation. This way, as you progress with the book you will be able tocompare your initial judgment with weaknesses and smells I will expose and deal with later on.

My intention is to present you with a realistic scenario. I have even written down a few conversationswith the client so you can put yourself in the same situation — that of analyzing and distilling rawinformation from typical laymen who might want to use your product.

I also introduce another character in this chapter. Tim is our typical apprentice programmer,already in command of basic Visual Basic programming techniques, but not at all familiar withrefactoring or some of the more complex design principles. He will eagerly get the job done, but, as you will see throughout the book, this is not sufficient for well-designed, refined, industrial-strength applications. In this chapter I will let Tim explain in his own words the work he has performed on the Rent-a-Wheels prototype implementation.

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 87

Page 123: Professional Refactoring in Visual Basic

I suggest you download the complete project from www.wrox.com and use the IDE to follow as we goalong. I have included a lot of code listings in the text, but you’ll have an easier time finding your wayaround if you use Visual Studio with the complete source code at hand.

I’ll start presenting the application right from the inception. This way, you can see the business casefor the application and observe work on the application in progress. Bear in mind that this chapter isnot meant to teach you software design or analysis. While you are reading it, I hope this chapter doestwo things:

❑ I hope it helps you feel as if you’re having a typical day in the office and using some very familiar techniques and styles.

❑ Then, I hope to use this familiarity as an entry point to discuss some typical VB approaches toprogramming and how they relate to refactoring and software design in general.

Inter viewing the ClientThe client is a vehicle rental company. It needs to automate and computerize its operation to make its process more efficient. It needs to cut the time it takes to serve each client, to speed up the vehiclereception process, and to get rid of unnecessary paperwork.

The first task I undertook was to speak to different people employed at the company. This way I gatheredthe information I needed to define the requirement and start the project. The next sections show the tran-scripts of my conversations with Rent-a-Wheels employees.

Interviewing the Manager My first meeting was with the manager. His opinion has the most weight on the project since he willmake all the final decisions regarding the application. Here is what he had to say:

“Rent-a-Wheels is a successful car rental company. We are more than 10 years in the market and havemanaged to position ourselves well. Our biggest strength is a loyal clientele and a new fleet, since wehad a big investment come in earlier this year. At the same time, we started another location. We openedup the airport branch.

“This challenged our operations in a new way. You see, there is a lot of competition at the airport, and more than a few rental desks are placed next to each other. We have noticed that as soon as a lineforms the clients start going over to the competition. The time we take to serve a single client is justtoo long. I spoke to our staff and they tell me that our existing system, based on pen and paper, is just too tedious. The competition does everything on the computer. One company recently acquiredbig flat-panel monitors. There is a free wireless Internet connection covering the airport, so we can rely on that for connectivity.

“We also waste time on reception. The desk is informed that a vehicle is available only after the inspec-tion and cleaning of that vehicle is done. It doesn’t take long, but still we lose customers in the process.We need to do something about it; we need to sell more!”

88

Part I: Introduction to Refactoring

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 88

Page 124: Professional Refactoring in Visual Basic

This talk with the manager provided some insight into the task facing me:

❑ First of all, it seems the company needs an efficient system to manage the fleet and keep track ofthe availability of the vehicles, and to present this information at the reception desk.

❑ Another important point I noticed is that the company has multiple branches. The system willhave to consider that also.

However, while talking to the manager gave me more insight into my objectives, it failed to providedetailed information on the way business is conducted. So the next person I had to interview was the receptionist.

Interviewing the Desk ReceptionistThe desk receptionist works with clients directly. She gave me more information about the business process.

Me: I guess the first thing you look at is what vehicles are available when clients call or come over?

Desk Receptionist: Yes, we have all vehicles divided into different categories. Clients generally ask for a category or specific model — that’s what the price depends on. Other details, like color, come up lessfrequently. I maintain a list of all the vehicles in Excel, and I mark them as they get rented or returned.The problem is that clients can return their vehicles to any branch they want, so I end up updating the list all the time.

Me: Okay, great, let me make a note of that . . . I understand you have a lot of paperwork?

Desk Receptionist: Oh, yes. You mean you want to know more about the receipts?

Me: Yes, right, what kind of receipts?

Desk Receptionist: “When I finish taking all the data from the client, I issue him a receipt that he hasto deliver to our people at the parking lot in order to get the car. And when he returns the car, they use the same receipt to write down additional charges that might apply, like empty tank, mileage, etcetera. I guess that’s about it.

Me: That was very helpful. Thanks a lot!

I have more material to work with now.

❑ I know I need to manage vehicles and mark when they are rented or returned.

❑ I know I need to maintain vehicle information like model and color.

❑ I know that all vehicles belong to a certain category, and that the price of the rental depends on it.

If PCs are in all the right places, the application I provide should be able to eliminate a lot of paperwork.Next, I determine it’s best that I talk to another party intimately involved with this process, one of theparking lot attendants.

89

Chapter 4: Rent-a-Wheels Application Prototype

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 89

Page 125: Professional Refactoring in Visual Basic

Interviewing the Parking Lot AttendantIt is sometimes the case that no single employee is familiar with the whole business process in detail.Sometimes the best way to collect requirements is to hear the facts from different sources in order topiece together the whole story. This is the reason I decided to speak to employees in any position thatmight benefit from the application. A parking lot attendant should provide some insight into the way the lot is operated.

Me: The customers pick up the cars here?

Parking Lot Attendant: Yes, my duty is to hand over the car to the customer and to receive it back. Thecustomer has to give me a receipt so I know he finished all the proceedings at the reception desk. I thenwrite down the mileage and put the receipt in the “Rented Vehicles” folder. When the customer returnsthe vehicle, I check the mileage and the tank and write the numbers on the receipt. We give the customera bonus if he returns the car with the tank full.

Me: And what happens if the tank is not full?

Parking Lot Attendant: Oh, the maintenance guy has to fill up the tank, and this is charged in additionto other items. No bonus in that case, of course. Plus, the customer gets to wait if he is not satisfied withwhat I wrote down. I can only write down full, three-quarters, one-half, one-quarter, or empty fromlooking at the indicator. If the customer thinks this is not correct, he has to wait for the maintenance guyto come back with the receipt from the filling station. After the reception, the vehicle has to pass througha regular inspection by our maintenance personnel. I check for any obvious damage when the vehicle isreturned, but I am not in charge of the maintenance.

The story seems to be more complete now.

❑ The application needs to keep information on mileage for each rental.

❑ The application needs to keep information on the tank level after the vehicle has been returned.

But my conversation with the parking lot attendant has made me realize that to get the complete story, I must speak to still another party — a member of the maintenance personnel.

Interviewing Maintenance PersonnelThis is the last employee I need to speak with. By talking with this employee, I hope to learn more aboutvehicle inspection and maintenance procedures.

Me: Your job is to inspect vehicles when they are returned?

Maintenance Worker: Yes. I have to check for any damage, and I need to make sure that the tank is full.I also keep records on regular maintenance that is performed after a certain number of miles. If the timeis up, I have to take the car to the service center. I have to let the people at the desk know so they areaware that that particular car is no longer available. Since we let our customers return the vehicle to anybranch they like, sometimes at the end of the day I have to take cars back to the branch they came from.

Me: Okay, thank you for your time.

90

Part I: Introduction to Refactoring

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 90

Page 126: Professional Refactoring in Visual Basic

From this final conversation it is clear that vehicles are regularly removed from the fleet in order to receiveroutine maintenance. The inspection performed after the customer returns the vehicle is mandatory.

Now it is time to compile all the information I have gathered and to prepare the proposal I am going topresent to the folks back in my office.

Taking the Initial Steps in the Rent-a-Wheels Project

I decided to start the work by identifying the principal use cases. This will serve as a starting point forthe prototype that I will use to obtain the final go-ahead from the client. While I am at it, I might as wellsketch a few diagrams — it always gets a nod of approval from the boss.

Actors and Use CasesThe first step is to identify all the future users of the system, or the actors. This is not so difficult. All Ineed to do is follow the “paper trail” I discovered in my interviews. The receipt was traveling from thedesk to the parking lot and back. Additionally, the maintenance personnel had to make a report if the carhad to be filled up or sent to a service center. I decided to depict actors and principal use cases with adiagram. Figure 4-1 shows the Rent-a-Wheels use case diagram I came up with.

Figure 4-1

After I identified the principal use cases, I decided to write down all the steps the actors have to performwhile interacting with the application in order to reach the final goal. This will provide a good breakdownof the functionality the Rent-a-Wheels application will have to offer.

Receptionist Parking Lot Attendant

Maintenance Personnel

Rent vehicle

Receive payment

Hand over vehicle

Receive vehiclefrom customer

Inspect vehicle

Receive vehiclefrom other branch

91

Chapter 4: Rent-a-Wheels Application Prototype

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 91

Page 127: Professional Refactoring in Visual Basic

Rent Vehicle1. Receptionist selects the vehicle from the list of available vehicles.

2. Receptionist inputs the customer data: ID type and number.

3. Receptionist marks the vehicle as “hand over.”

Receive Payment1. Receptionist selects the vehicle from the list of “payment pending” vehicles.

2. System displays the charges.

3. After the payment has been made, receptionist marks the vehicle as “available.”

Precondition: Customer has to return the vehicle to the parking lot first. (See the use case“Receive the Vehicle from the Customer.”)

Hand Over Vehicle to Customer1. Parking lot attendant searches for vehicle marked as “hand over” that has customer’s ID.

2. Parking lot attendant marks vehicle as “rented.”

Precondition: Customer has to finish all formalities at the desk first. (See the use case “Rent a Vehicle.”)

Receive Vehicle from Customer1. Parking lot attendant searches for rented vehicle in the vehicle list.

2. Parking lot attendant inputs mileage, tank level, and any additional comments.

3. Parking lot attendant marks vehicle as “payment pending.”

Inspect Vehicle1. Maintenance personnel select vehicle being inspected from the list.

2. Maintenance personnel mark vehicle as “in operation.”

Alternative:

2a. If maintenance is due, vehicle is marked as “in maintenance.”

Receive Vehicle from Other Branch1. Parking lot attendant searches on list of vehicles for vehicle coming from another branch.

2. Parking lot attendant marks vehicle as stationed in the receiving branch.

Receive Vehicle from Service Center1. When vehicle is returned from the service center, it is marked as “in operation.”

These use cases give a pretty good overview both of the business process at the company and of the wayin which users should interact with the future system.

92

Part I: Introduction to Refactoring

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 92

Page 128: Professional Refactoring in Visual Basic

One thing became rather obvious after all this: the word vehicle was present in all the use cases.Understandably, most of the business in the Rent-a-Wheels company revolves around this entity. Thevehicle has characteristics like license plate number, category, make, model, and color. As a part oftheir work, employees have to maintain information about each vehicle and, especially important,mark the vehicle as available, hand over, payment pending, or in maintenance as it goes through variousbusiness processes. Since this state of the vehicle is at the center of the business process I am analyzing,I have decided to investigate it a little bit further.

Vehicle StatesAs a vehicle is selected and becomes involved in the business flow, it passes through a few states in awell-defined order. As you analyze those states, you can see that their nature is twofold: some states areconcerned with the vehicle in regard to its being rented to a customer and others in regard to its going tomaintenance. I decided to use two diagrams to represent these states and transitions. In Figure 4-2 youcan see the states the vehicle goes through during a normal rental process.

Figure 4-2

Rules related to vehicle states and state transitions embody the core of the business process in the Rent-a-Wheels company. The following list explains these states and the transitions related to normal vehicleuse in more detail:

❑ The receptionist can rent only available vehicles.

❑ The parking lot attendant can hand over to customers only the vehicles marked hand over.

❑ The parking lot attendant can receive only rented vehicles.

❑ The receptionist can charge for only payment pending vehicles.

In Figure 4-3 you can see the states related to vehicle maintenance.

Available Hand over

Payment pending Rented

93

Chapter 4: Rent-a-Wheels Application Prototype

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 93

Page 129: Professional Refactoring in Visual Basic

Figure 4-3

States and transitions describing the maintenance process represent another important set of businessrules. The following list explains states related to the maintenance operation in more detail:

❑ The receptionist can rent only vehicles marked in operation.

❑ If a vehicle is due for maintenance or presents problems, maintenance personnel mark it as in maintenance.

❑ Once the vehicle is back from maintenance, it’s marked as in operation.

So, is there any relation between these two sets of states and transitions? Naturally, only operational vehiclescan be rented, handed over to customers, received, and charged for. So available, hand over, rented, and pay-ment pending are inner states of the in operation state. Or, to put it differently, in operation is a superstate, andthis can be represented with a single state diagram using nested states. The single state diagram is presentedin Figure 4-4.

With all that work completed, I knew it would be good to finalize the work I had done by materializingall these ideas into some sort of user interface prototype.

First Sketch of the Main Application WindowWith all this information in hand, it’s time to take a shot at drawing the main application window. Thecentral point of the application is the fleet and the different states of the vehicles. I can show vehicles in acertain amount of detail in a grid, and let users operate on a specific vehicle by selecting a single row inthe grid. Each row represents a single vehicle. The user will be able to operate on the vehicle by selectingit on the grid and then pressing the appropriate button — Rent, Hand Over, Charge, or similar. I also needto provide for filtering of the vehicles in the list. That will make jobs a lot easier for the users, as they willbe able to restrict the display to vehicles they are interested in, based on vehicle state or some other char-acteristic. Figure 4-5 represents the main application window.

In operation

In maintenance

94

Part I: Introduction to Refactoring

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 94

Page 130: Professional Refactoring in Visual Basic

Figure 4-4

Figure 4-5

Available Hand over

Payment pending Rented

In maintenance

In Operation

95

Chapter 4: Rent-a-Wheels Application Prototype

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 95

Page 131: Professional Refactoring in Visual Basic

I thought that use cases, state diagrams, and a main window prototype gave a good starting point for theapplication I was about to build, so for the next step I decided to present these artifacts at the team meeting.

Rent-a-Wheels Team MeetingThe Rent-a-Wheels team at this stage consisted of three persons.

❑ The developer — Me, of course.

❑ A project manager — He was not dedicated full-time to the project.

❑ A VB programmer — In this case it was Tim, a novice VB programmer, but one quite up to thetask, and very efficient as a coder.

I started the meeting by presenting the information gathered so far. First, I gave them some backgroundinformation about the company, the meetings I had had, and the people I had met. Then I presented thework I had done on the analysis; I showed them the diagrams and talked about use cases. I even presentedmy first try at a main application window.

Because I was about to go on my vacation, we agreed that Tim should take over the application. Hewould continue to work on the analysis and take a first shot at the prototype. When I got back, I would join the team again and act as lead programmer.

Upon my return, I got back to working on the Rent-a-Wheels application. During my absence, the prototypehad been completed, presented to the client, and initial approval was received. While assembling the proto-type, Tim went on to implement most of the functionality initially described. Tim’s idea was to try to reusethe code from the prototype in order to cut implementation time. This is fine, as long as the code is up tostandards and well designed. To make sure it is so, I have to take a detailed look at the application.

Making the Prototype WorkTo begin, I asked Tim to fill me in on the progress he had made while I was away.

Tim said, “I used the information you prepared during the analysis to build the prototype. I built astandalone application that connects to a Microsoft SQL database over ADO .NET. I decided not to usedata binding; I had enough time to go for a more robust solution. Basically, there is one central window,based on the drawing you made. But I guess you are really interested in the database model. We had better start off there.”

Examining the Database ModelTim continued to fill me in: “The database engine is MS SQL 2005. The database name is RENTAWHEELS.It has the following tables: Vehicle, Category, Model, and Branch. I chose to use the license plate num-ber for the primary key in the Vehicle table. The rest of the tables have an automatic, auto-incrementingprimary key. The relationships are easier to observe in the diagram.”

Tim produced the Rent-a-Wheels database diagram shown in Figure 4-6.

96

Part I: Introduction to Refactoring

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 96

Page 132: Professional Refactoring in Visual Basic

Figure 4-6

He said, “Each vehicle pertains to a single branch. Each vehicle model is placed under a certain category.Category has attributes that contain the vehicle’s price information: daily, weekly, and monthly price.You can find out the price for a certain vehicle by looking at its model — Model belongs to a categoryand Category has the price information. When writing SQL code, this translates into an Inner Join ofthe Vehicle table with the Model table and an Inner Join of the Model table with the Category table.

“The Vehicle table also contains information related to the rental state of the vehicle: it’s the columnnamed Available. Information related to the operational state of the vehicle is held in the column namedOperational. I used the tinyint type for these two columns. In the case of Available, the meaning ofthe data is shown in this table.” Tim showed me Table 4-1.

Table 4-1: Connotation of Column “Available” tinyint Values in the Vehicle Table

Available Column’s tinyint Value Meaning

0 Available

1 Hand Over

2 Rented

3 Charge

97

Chapter 4: Rent-a-Wheels Application Prototype

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 97

Page 133: Professional Refactoring in Visual Basic

Tim said; “You will notice that I decided to use a more compact name for the Payment Pending state.Charge fits better on the fleet display grid on the Fleet View form.”

Then Tim went on, “The Operational column can have only two values.” Then he showed me Table 4-2.

Table 4-2: Connotation of the Operational Column’s tinyint Values in the Vehicle Table

Tim then said, “Other attributes of the vehicle are related to customer information about the customercurrently renting the vehicle: names, document type, and numbers. These are used by the parking lotattendant to identify the customer at the moment when the vehicle is handed over. When the vehicle isreturned, tank level and mileage are saved in Tank and Mileage columns. Of course, this data is relatedto a single rental, and the current customer has to be erased once the customer has paid for the vehicle.

“Like the Available and Operational columns, the Tank column uses different tinyint values withspecial meanings.” Tim then showed me Table 4-3.

Table 4-3: Connotation of the Tank Column’s tinyint Values in the Vehicle Table

Tim concluded, “Those were the most important facts in regard to the database.”

I asked, “What about other database objects? Did you use any stored procedures, views, or triggers?”

Tim said, “No, this is it. I spoke to the client and he was adamant about keeping the application as data-base-independent as possible. They still have to buy software licenses and they want to be able to lookfor the best price on the market. I thought that minimizing the code on the database side will help us notdepend on any particular database model. That’s why I have placed all the SQL code inside the VB app.”

With all that clear, I could then examine the VB code.

Tank Column’s tinyint Value Meaning

0 In Operation

1 In Maintenance

2 1/4 Full

3 1/2 Full

4 Full

Operational Column’s tinyint Value Meaning

0 In Operation

1 In Maintenance

98

Part I: Introduction to Refactoring

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 98

Page 134: Professional Refactoring in Visual Basic

Examining the Visual Basic CodeTim explained, “There are a few forms in the project, but the main form is the one with the Fleet view,enabling the user to monitor vehicles and select them in order to perform the most important operationsfor the business. It is the same form you designed in the analysis phase of the project. The only new func-tionality I added is the administration menu. It is used to invoke forms that serve to administer differenttables in the database. This way the user can add a vehicle, delete it, or edit existing vehicles. The samegoes for categories, models, and branches. Let me show you the menu.”

Tim opened Visual Studio IDE and ran the application. He showed me the Fleet view with the dataadministration menu he had added (Figure 4-7).

Figure 4-7

Tim continued, “On the right side are the buttons that employees will use to perform their everydaywork — a Rent button, a Hand over button, and so on. When they press the button, in some cases, the oper-ation is directly performed, and in other cases, when additional information is needed, a new form is dis-played. For example, we can take a look at the code being executed when the user presses the Rent button.”

The code that handles the Rent button click event is presented in Listing 4-1.

Listing 4-1: Event-Handling Routine for BtnRent_Click from the Fleet View

Private Sub BtnRent_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles BtnRent.Click

Continued

99

Chapter 4: Rent-a-Wheels Application Prototype

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 99

Page 135: Professional Refactoring in Visual Basic

Listing 4-1: Event-Handling Routine for BtnRent_Click from the Fleet View (continued)

‘Check that user has made a selection If DGridFleetView.SelectedRows.Count > 0 Then

‘Read value from first cell as Vehicle Id and assign ‘it to Text property of TxtLP textobox in FrmRentFrmRt.TxtLP.Text = _ DGridFleetView.SelectedRows.Item(0) _ .Cells.Item(0).Value ‘Show FrmRtFrmRt.Show()

Else‘Warn user if no selection made in table and exitMsgBox(“Please select vehicle first!”)

End IfEnd Sub

Tim said, “It is easy to follow the code because I spent a lot of time commenting on it in detail. First, a check is made in order to ensure that the user has selected a vehicle in the grid. Then, a license plate numberfrom the first cell in the selected row is assigned to a text box in the FrmRt form, and this form is displayed.And just in case the user didn’t make his or her selection before pressing the button, a message box with areminder is displayed. Take a look at the FrmRt form.”

He showed me the rental form he had created, named FrmRt, which is shown in Figure 4-8.

Figure 4-8

Tim said, “After entering the required data, the user performs the operation by pressing the Rent button.”Listing 4-2 shows the code for handling the button click from this form.

Listing 4-2: Event-Handling Routine for BtnRent_Click from the Rent Form

Private Sub BtnRent_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnRent.Click

‘Declare variablesDim oCnDim oCmd‘double-check with userIf MsgBox(“Are you sure?”, MsgBoxStyle.OKCancel) Then

‘Activate error handlingOn Error GoTo ErrorHandler

100

Part I: Introduction to Refactoring

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 100

Page 136: Professional Refactoring in Visual Basic

Listing 4-2: Event-Handling Routine for BtnRent_Click from the Rent Form (continued)

‘Create SqlConnectionoCn = New SqlConnection(“Data Source=TESLA-DAN;” + _ “Initial Catalog=RENTAWHEELS;User ID=sa”) oCmd = New SqlCommand ‘Create Sql String with parameter @SelectedLPstrSql = “Update Vehicle “ + _

“Set Available = 1,” + _ “CustomerFirstName = @CustomerFirstName,” + _ “CustomerLastName = @CustomerLastName,” + _ “CustomerDocNumber = @CustomerDocNumber,” + _ “CustomerDocType = @CustomerDocType “ + _ “WHERE LicensePlate = @SelectedLP”

‘open connectionoCn.Open() ‘Set connection to command oCmd.Connection = oCn ‘set Sql string to command object oCmd.CommandText = strSql‘Add parameter to command oCmd.Parameters.AddWithValue( _ “@CustomerFirstName”, TxtFirstName.Text) oCmd.Parameters.AddWithValue( _ “@CustomerLastName”, TxtLastName.Text) oCmd.Parameters.AddWithValue( _ “@CustomerDocNumber”, TxtDocumentNo.Text) oCmd.Parameters.AddWithValue( _ “@CustomerDocType”, TxtDocumentType.Text) oCmd.Parameters.AddWithValue( _ “@SelectedLP”, TxtLP.Text) ‘execute commandoCmd.ExecuteNonQuery() ‘close connectionoCn.Close() ‘destroy objects oCmd = Nothing oCn = Nothing Exit Sub

ErrorHandler: MsgBox(“A problem occurred and “ + _

the application can not recover! “ + _ “Please contact the technical support.”)

Err.Clear() End If

End Sub

Tim explained his approach in this code: “As a first step, I give the user a chance to change his mind, just incase he pressed the button by mistake. Then I initialize the connection. I am using a .NET Framework DataProvider for SQL Server to connect to the database; since the company has Microsoft SQL Server 2005, this isthe most efficient provider. After that I build SQL code as a string and assign it to a command. Then the datais recollected from the form, and values entered by the user are added as command parameters. Finally, thequery is executed. At the end, I close the connection and clean up the objects.”

101

Chapter 4: Rent-a-Wheels Application Prototype

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 101

Page 137: Professional Refactoring in Visual Basic

When I asked Tim about error handling, he said, “Below the ErrorHandler label you can see the codethat gets executed in case the error occurs. This can happen if the database server is down, if there is aconnectivity problem with the database, or something like that. Basically, users are informed that anerror has occurred and that they should contact technical support. The whole method is quite straight-forward really, just a SQL query executed against the database.

“So, basically, that’s it. If you look at rest of the code, it pretty much follows the same pattern.”

I said, “Yes, thanks, Tim. I’ll take another look at the code later on, but I think I have a pretty good ideaof what is going on in the application. Good job!”

With that, my initial conversation with Tim about Rent-a-Wheels ended, and at this point you, too,should have a pretty good idea of what is going on in the application that I will use as an examplethroughout the book.

If you are interested in digging further into the example application at this point, more conversationswith Tim about the details of the application can be found in Appendix B, and you are welcome to take alook at those. Of course, the source code for the application is available for download at www.wrox.com,and again I encourage you to download it if you haven’t already. However, for the purposes of usingthis example as a real-world scenario for discussion, this is enough to start with. Accordingly, in the nextsection I want to talk about what this example shows and how it applies to the topic of refactoring. Inthat section I want to list only a few typical pitfalls VB programmers can be dragged into with theirdesire to get results immediately. While I have great respect for pragmatic programmers and I am notkeen on wasting my time on some vague philosophical discussions, I am very interested in producingthe best possible code. For that purpose, it is crucial you understand the fundamental principles behindobject-oriented programming and how sometimes the fastest approach is not the best approach, noreven the most simple.

Fast and Furious, a VB Approach to Programming

In many respects, the application you just saw is a very typical VB application. Not too much time hasbeen spent on formally designing the application before, during, or after construction. Most of the designtime was invested in blueprinting the database. In this case, you can assume from the lack of any otherdesign-related artifacts that programmers embarked upon coding from the outset. This is not necessarilya mistake if it is followed by design-related improvements during the development process; otherwiseyou can end up with some serious pitfalls in your code. Here are some of the typical approaches used onRent-a-Wheels that can lead to drawbacks in your code if not counteracted by the application of object-oriented design principles.

Database-Driven DesignSince the database was the first component of the application to be given definite shape, the rest of thecode had to comply with the decisions Tim made in constructing the database. The bulk of VB code is, infact, interested only in interacting with the data store. I call this a database-driven design.

102

Part I: Introduction to Refactoring

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 102

Page 138: Professional Refactoring in Visual Basic

Relational database design is governed by a different set of principles from object-oriented design. Whendesigning databases, a designer is concerned with providing efficient data storage and retrieval, enforcingdata integrity, providing transactional consistency, and so on. By putting effort into database design with-out structuring your VB code according to object-oriented principles, and being driven by data designinstead, you will end up with poorly designed VB code. Often it is SQL code that is used to express busi-ness logic. Such a design can have an efficient data backend, but your VB code will be inefficient and diffi-cult to maintain.

Modern applications use an object-oriented layer as the core layer for implementing business logic. Object-oriented code is much more powerful and efficient for expressing business rules. Later on in this book youwill see how you can express business logic in object-oriented terms and keep the database layer only fordata storage and retrieval, an area where databases are on their own turf.

GUI-Based ApplicationIn essence, this application is a standalone executable connecting to a remote database. It is a typicalclient-server application. The .NET platform provides a toolkit called Windows Forms for writing ele-ments of the user interface, and it is exactly this toolkit that Tim used to construct the user interface forthis application. As a matter of fact, the GUI was probably the only other part of the application besidesthe database to go through a more elaborate design process. You can see that most of the administrationforms are very similar in appearance, because they follow the same pattern.

Another interesting detail crops up if we look at the type of classes used in the application. All classesthat were created to extend the Form class (Inherits System.Windows.Forms.Form), are part of theuser interface, and have been designed in Windows Forms Designer. This is apparent when you openthe code generated by Windows Forms Designer by clicking the class name in the Class View window.Windows Forms Designer generates partial classes and all code generated by the tool is in a separate file.

All the code programmed was added to form classes; no other classes or structures were added. That iswhy we can say that this application is GUI-based.

Modern applications are often tiered and component-based. Such a modular design is a vast improve-ment over the legacy client-server design. You will benefit from improved reuse, simplified maintenance,simplified distribution, application modularization, and even shorter testing and compilation time. Inorder for you to be able to construct applications in a modular manner, the typical approach is to dividean application physically in the form of components or dynamically linked libraries across tiers. Whenyou are depending only on GUI classes produced by Windows Forms or some other visual designer,such organization is not possible.

Event-Driven ProgrammingVisual Basic programmers are very familiar with the concept of event-driven programming. This stylewas present in VB a long time before the .NET platform appeared. In essence, the tool is capable of gen-erating a hook, an empty event-handling routine declaration. It is up to a programmer to fill in the bodywith the code that gets executed when a certain event, a consequence of user interaction with the appli-cation, occurs.

103

Chapter 4: Rent-a-Wheels Application Prototype

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 103

Page 139: Professional Refactoring in Visual Basic

This is exactly the approach taken in constructing the Rent-a-Wheels application. Most of the code is placedinside event-handling routines, in most cases a Click event of some button. Rent-a-Wheels is typical ofevent-driven programming.

The problem arises when the programmer does not go beyond the routines generated by the tool. If youare not structuring code further, into separate methods and classes, there is bound to be a lot of repeti-tive and duplicated code. Such code will make your maintenance much more difficult, and your designbloated and inefficient. Again, the solution is in using the object-oriented approach and restructuringyour code in keeping with object-oriented principles.

Rapid Application Development (RAD)The Rapid Application Development (RAD) approach is meant to empower the programmer withtools and techniques that can radically cut development time. This is possible thanks to visual design-ers, wizards, and code generators. Another benefit is the tool capacity to create prototypes early in thedevelopment cycle.

You have seen this approach at work with the Rent-a-Wheels application. Tim was able to construct a fully functional prototype in a short time. This prototype will be reused to create a final version for production.

While this approach is very suitable for constructing quick and dirty prototypes and some very simpleapplications, it shows weaknesses at different scales when you are working with more complex applica-tions. This is because of the code proliferation and reduced code reuse often found in RAD code: theseweaknesses can easily outweigh the benefits of RAD if they are not mitigated with thoughtful, object-oriented design.

Copy-Paste as a Code Reuse MechanismAs you gain more experience, you soon begin identifying the repetitious patterns and recurring prob-lems you deal with while you program. Naturally, you soon start developing techniques that can helpimprove your productivity and avoid repetition. One of the first techniques you adopt for this purposeis copying and reusing sections of code that can be adapted to a new use with a minimum of modifica-tions. This can be a great productivity booster and is an important confidence-builder for a novice pro-grammer. There is almost nothing as satisfying to an inexperienced programmer as being able to finisha new task quickly by reusing work done on some other project, done in some other part of the applica-tion, or simply found on the Internet.

While this is an important improvement in programming technique and productivity for an aspiringprogrammer, it has serious downsides. Copy-paste as a reuse mechanism leads to code proliferationand duplication that can soon turn into a real maintenance nightmare. Copy-paste can be seen as a firststep on a programmer’s path to advanced skill acquisition. The same capacity for pattern recognitioncan lead to more advanced reuse mechanisms. Instead of copying a code section, the programmer canmethod and call extract a method call it from multiple locations. An object can be instantiated and used,a class inherited, and so on. Standard object-oriented reuse mechanisms are more powerful and bringgreater benefits than simple code copying. Refactoring deals with this issue and teaches you how to useyour pattern-recognition skills in a more powerful way.

104

Part I: Introduction to Refactoring

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 104

Page 140: Professional Refactoring in Visual Basic

From Prototype to Deliver y Through the Refactoring Process

All in all, the prototype was fully functional and had already received user approval. This means thatthe important work had already been completed successfully. The prototype represented a valuable basefor the final delivery. However, at this stage it was important to address the design issues that this appli-cation presented. It needed to be refactored. You will see the results of that refactoring work as I returnto the Rent-a-Wheels example throughout the book.

SummaryVB programmers often create prototypes very early in the development phase. They are able to materializethe application early on and present it to the client. This way a lot of risk regarding requirements fulfillmentcan be mitigated quickly.

This chapter presented a real-life scenario that will serve as an example application for the book. It showedhow the prototype for the Rent-a-Wheels application was created. First, requirements-gathering includedinterviewing the employees of the company. A manager, receptionist, parking lot attendant, and mainte-nance worker all had their say about the work they perform and the most important issues they expect thefuture application to resolve.

Facts gathered from the interviews were formalized into the most relevant use cases. The vehicle wasdetermined to be a central entity in the business process. The vehicle’s different states result from theoperations that employees perform.

Based on the analysis performed, a prototype for Rent-a-Wheels application was created, addressing most of the functionality needed. The application was implemented as a standalone executable that connects to a Microsoft SQL database. Upon examination, it became obvious that the prototype was constructed inline with the tradition of VB programming. The database was designed up front, the GUI was then designedwith the Windows Forms Designer in Visual Studio, and in the end the coding was performed by filling inthe event-handling routines generated by the tool.

While the work performed is valuable and the prototype resolved most of the business-related problemsthe application was meant to address, it nevertheless has numerous flaws. It will have to be refactoredbefore it can reach the final phase.

As you move forward in this book and learn new refactoring techniques and code smells, I’ll come back tothe Rent-a-Wheels code created in this chapter and apply your newly acquired refactoring knowledge to thisclose-to-real-life sample application.

105

Chapter 4: Rent-a-Wheels Application Prototype

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 105

Page 141: Professional Refactoring in Visual Basic

79796c04.qxd:WroxPro 2/23/08 7:46 AM Page 106

Page 142: Professional Refactoring in Visual Basic

Part II: Preliminary VB Refactorings

In the next few chapters I am going to explore preliminary VB refactorings. I callthem preliminary because you can execute them even without deeper knowledgeof the problem domain the code is meant to resolve. They are mostly performed on the syntactic level and deal with problems that have their origin in VB backwardcompatibility or are related to good programming practices that I call “basic pro-gramming hygiene.” These refactorings let you clean and prepare the code for thestandard refactoring techniques we’ll talk about in Part III.

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 107

Page 143: Professional Refactoring in Visual Basic

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 108

Page 144: Professional Refactoring in Visual Basic

Chameleon Language: FromWeak Static Typing to

Strong Dynamic Typing

Almost all high-level programming languages implement the concept of types. By classifyingvalues and expressions into types, you achieve a number of benefits: safety, optimization,abstraction, and modularity.

Most modern programming languages can be placed into one of two categories: statically typed ordynamically typed.

❑ In statically typed languages, type resolution is performed at compile time, and typeinformation is provided explicitly in the code by the programmer himself in the form of a variable declaration.

❑ In dynamically typed languages, data types are not declared, and type information is notavailable until execution.

Visual Basic can be statically or dynamically typed. This behavior of VB’s compiler is controlled bythe Option Explicit statement.

Languages are also differentiated by the level of type safety they provide. Strongly typed languagesdisallow operations on arguments that have a wrong type. Other type languages permit these opera-tions by implicitly casting the types of arguments so that the operations in question can be performed,guided by rules that take both operands into account. The disallowing or allowing of implicit conver-sions in VB code is controlled by VB’s Option Strict statement.

In Visual Basic 2008 there is new and different type of Option statement. Option Infer letsyou omit the As clause of your local variable declaration, but the code is still statically typed. In thatsense it is more of a productivity feature than an additional type-system behavior. Since I deal withVB 2008 novelties in Chapter 15, I have left the discussion of Option Infer for that chapter.

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 109

Page 145: Professional Refactoring in Visual Basic

Table 5-1 shows the kinds and levels of typing enforced in VB by the compiler when the OptionExplicit and Option Strict options are active or inactive.

Table 5-1: VB’s Option Statement in Relation to Typing Enforced by the Compiler

This versatility of VB is unique and enables the effective use of VB for variety of purposes, from fast pro-totyping to industrial-strength applications. While .NET as a platform is mostly statically oriented (otherlanguages like C# and managed C++ are statically typed), the flexibility of VB lets it fill the gap in thedynamically typed compartment.

However, in some cases, when programmers are not completely aware of the effect that Option state-ments can have on the code, or when they are working with legacy code upgraded to VB .NET, they canproduce potentially problematic code. Such code comes in two flavors:

❑ Code written in statically and strongly typed style, but without compiler enforcement of static orstrong typing (Option Explicit Off and Option Strict Off but type declarations present)

❑ Code written in statically and strongly typed style, but without compiler enforcement of strongtyping (Option Explicit On and Option Strict Off but type conversions not present)

In this chapter you are going to see in detail the effect that Option Strict and Option Explicitstatements have on your code.

❑ You will see the benefits of strong static typing.

❑ I will show you ways to transform the problematic code, written in strong static style but with-out compiler enforcement, into code in which compiler checking is activated.

❑ You will also see how dynamically typed code can be useful in some circumstances.

❑ You will see how to combine statically and dynamically typed code in the same project, reapingthe benefits of both styles.

❑ Finally, I will show you the alternatives you have when setting Option Strict and OptionExplicit, and the role Visual Studio can play in this task.

Option Explicit and Option Strict, the .NET Effect

The effect that activating the VB compiler options Option Strict and Option Explicit has on the wayyou write your code is profound, but it is often overlooked. This is probably because of default settings —they let you program without having to take care of some type-conversion issues. The creators of VB have

Option Explicit On Option Explicit Off

Option Strict On Static strong Dynamic strong (rare)

Option Strict Off Static weak (problematic) Dynamic weak

110

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 110

Page 146: Professional Refactoring in Visual Basic

given you the ability to ignore a number of issues as you write code so that you can be productive fast, butsuch an approach can have some very negative sides to it. If you take a look at the official VB documenta-tion, these issues are seldom discussed in detail. I will explain them and give you sufficient information soyou are aware of the full impact of your decision to activate or deactivate these options.

When creating Visual Basic for the .NET platform, language makers had to take into an account a vastexisting code base written in VB6 and previous versions of VB. It was certainly important to providesome upgrade path for existing legacy code. On the other hand, one of the most sought-after features ofVB was strong static typing. Using strong static typing essentially means that you need to declare explic-itly all your variables and their types and that you have to take care of all type conversions.

In order to accomplish both goals, some type of upgrade path and support for static typing, the followingapproach was adopted: VB .NET can work both with and without static typing, depending on a compileroption value:

❑ If you deactivate Option Explicit and Option Strict, you do not have to declare vari-able type, or declare variables at all. Also, all type conversions are performed automatically,without your having to write any code that will deal with type conversions. This is dynamic,weakly typed code.

❑ If you activate Option Explicit and Option Strict, you are obliged by the compiler todeclare all your variables and their types, and all type conversions have to be performed delib-erately and written down explicitly in code. This is static, strongly typed code. I will refer to suchcode as strict (Option Strict On) or explicit (Option Explicit On).

The problem is that you can still write your code in a strongly and statically typed style even withoutactivating Option Explicit and Option Strict, but you don’t get any tool support for it. If you for-get to declare a variable or its type, or perform some potentially hazardous implicit conversion, the com-piler does not complain. Instead, it tries to work things out to the best of its abilities. I will refer to suchcode — written in a statically and strongly typed style, but with compiler type checking deactivated —as relaxed or permissive.

On the other hand, once these options are activated, you can count on the compiler to perform all thenecessary verifications and to compile the code only after all variables and types are declared and alltype conversions performed explicitly. Needless to say, this is the preferred way to write code. If you arewriting strongly and statically typed code, it only makes sense to count on the tool to provide you withcompile-time type checking.

Setting Option Explicit On in Relaxed CodeThe purpose of the Option Explicit statement is to tell the compiler to enforce variable declaration.After this option is set, if any variable is encountered that has not been previously declared with a Dim,Private, Public, or ReDim statement, a compilation error will be reported, the code will not compile,and the offending identifier will be underlined in the VB editor.

Note that even if you do not place Option Explicit On, you can still declare variables. The differencein this case is that you do not get the compiler to enforce the declaration of each and every variable.

111

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 111

Page 147: Professional Refactoring in Visual Basic

Now I want to discuss why omitting to declare variables can be considered bad style, how you can con-vert code with omitted variables into explicit code, and the you can apply this method to the Rent-a-Wheels application.

Understanding the Set Option Explicit On RefactoringAt this point you might rightfully ask, “Why declare variables at all?” Because you have seen that youcan write code without declaring variables, there must be some good reason to do so. Otherwise, you areonly writing some redundant, unnecessary code.

A number of reasons exist for declaring variable type, but the most obvious one is that a number ofbugs, a lot of times just simple typos, can be discovered at compile time. Imagine you have the followingcode snippet:

printers = GetPrinters()printersTested = 0For Each printer In printers

printer.PrintTestPage()printersTeted += 1

NextMsgBox(printersTested & “ printers tested.”, _MsgBoxStyle.Information, “Printer test status”)

Take a good look at this code. Do you see any problem with it?

It is fairly easy to understand what this code is doing. However, what is not so easy is to see that it con-tains a simple typing error that can cause you to spend a lot of time with a debugger in search of thisvery nasty type of insect.

Take look at this code again. This time, the offending line is shown in bold so you don’t have to spendany more time in futile hunt for this evasive enemy of every bug-free, code-loving programmer.

printers = GetPrinters()printersTested = 0For Each printer In printers

printer.PrintTestPage()‘Offending lineprintersTeted += 1

NextMsgBox(printersTested & “ printers tested.”, _MsgBoxStyle.Information, “Printer test status”)

If you do not enforce explicit variable declaration, the compiler creates a new variable each time a corre-sponding new identifier is encountered in the code. With the sample I have just shown, a compiler hasno way to guess that I actually meant to use the printersTested variable and not to create a newprintersTeted variable. So it goes on quietly minding its own business and creates a new variable.And I end up with a very undesirable bug in my code.

112

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 112

Page 148: Professional Refactoring in Visual Basic

So how does Option Explicit On help? Once you include the statement in your code, the offendingline is underlined as an error in the editor, and in the error list an appropriate error description is dis-played: “Name printersTeted is not declared.” Very clear, precise, and very helpful. You can then cor-rect the error right away. Finally, this is how the snippet should look once I use Option Explicit Onand correct the bug.

Option Explicit On‘...Dim printers = GetPrinters()Dim printersTested = 0For Each printer In printers

printer.PrintTestPage()printersTested += 1

NextMsgBox(printersTested & “ printers tested.”, _MsgBoxStyle.Information, “Printer test status”)

Once you have declared all the variables, you should proceed by assigning the corresponding type to thevariable. More about this in the next section.

Continued

Refactoring: Set Option Explicit On (Enforce Variable Declaration)MotivationExplicit variable declaration can prevent number of typo-related bugs early on at com-pile time. It can improve code readability by stressing variable scope.

Related SmellsUse this refactoring to eliminate the Implicit Variable Declaration smell.

Smell: Implicit Variable DeclarationDetecting the SmellUse the compiler to detect variables that are declared implicitly. Place Option ExplicitOn at the top of the file, compile the project, and look for a “Name someVariable is notdeclared” error in the Error window. The IDE will also underline the identifier in theEditor window if an undeclared variable is present.

Related RefactoringUse Set Option Explicit On refactoring to eliminate this smell.

RationaleFailing to declare variables explicitly can lead to difficult-to-detect bugs resulting fromsimple typing mistakes. It can also lead to a convoluted use of variables. Code read-ability can benefit from explicit variable declaration.

113

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 113

Page 149: Professional Refactoring in Visual Basic

Refactoring the Rent-a-Wheels Code to Explicit FormPerforming this refactoring on the Rent-a-Wheels application proves to be rather simple. After youplace Option Explicit On in a single file, Visual Studio IDE is very helpful in identifying undeclaredvariables. It seems that the application was written in an explicit style, but because the style was notenforced by the compiler a few implicit declarations need to be taken care of. Take a look at one samplefunction and the code that results once the refactoring is performed. Listing 5-1 shows the code for theBtnDelete_Click method. Code that changed after the refactoring is shown in bold.

MechanicsThe Option Explicit statement is module-level only. Option statements have to bethe first statements in the file. When transforming a project written in a relaxed vari-able-declaration style, start by placing the Option Explicit On statement in a singlefile. (If you set this option on at the project level, you are bound to receive a significantnumber of error messages, and increased clutter might slow you down.) After youdeclare all the variables in one file and no errors are reported by the IDE, move to thenext file. Complete this refactoring on the whole project before starting. Set OptionStrict On refactoring.

After you have turned Option Explicit on, the IDE will underline all undeclaredvariables and report errors (“Name VariableX is not declared”) in the error list. Work toeliminate errors one by one by declaring undeclared variables using Dim, Private,Public, or ReDim.

BeforePublic Function BasketTotal()

For Each item In Baskettotal += item.Price

NextReturn total

End Function

After‘Option Explicit On statement addedOption Explicit On ‘...Public Function BasketTotal()

‘Variable declaration addedDim total Dim item For Each item In Basket

total += item.PriceNextReturn total

End Function

114

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 114

Page 150: Professional Refactoring in Visual Basic

Listing 5-1: The Rent-a-Wheels FrmBranch Class BtnDelete_Click Method After theOption Explicit is Set to On

ByVal e As System.EventArgs) Handles BtnDelete.Click‘Declare variablesDim oCnDim oCmdOn Error GoTo ErrorHandler‘Create SqlConnectionoCn = New SqlConnection(“Data Source=TESLA-DAN;” + _“Initial Catalog=RENTAWHEELS;User ID=sa”)oCmd = New SqlCommand‘add parameter nameDim strSql = “Delete Branch “ + _

“Where BranchId = @Id”oCmd.Parameters.AddWithValue(“@Id”, TxtId.Text)‘open connectionoCn.Open()‘Set connection to commandoCmd.Connection = oCn‘set Sql string to command objectoCmd.CommandText = strSql‘exexute commandoCmd.ExecuteNonQuery()‘close connectionoCn.Close()‘destroy objectsoCmd = NothingoCn = NothingFrmBranch_Load(Nothing, Nothing)Exit Sub

ErrorHandler:MsgBox(“A problem occurred and the application can not recover! “ + _

“Please contact the technical support.”)Err.Clear()

End Sub

A single implicit declaration was resolved by the addition of a Dim statement at the beginning of the line.However, the variable type is yet not present.

Now take a look at a second, albeit more complicated, compiler option and the refactoring work you needto perform in order to activate this option and successfully compile the code, written in a permissive style.

Setting Option Strict On in Relaxed CodeSetting Option Strict On will affect your code in a number of ways. It will make you consider someaspects of programming you might have taken for granted. You will gain more control over the behavior ofyour program, but you will also have to put a little more work into the coding. By doing so, you will addresssome potential pitfalls that can cause some very subtle bugs in your code. You will put the compiler to work

115

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 115

Page 151: Professional Refactoring in Visual Basic

to disallow certain programming styles and constructs. Therefore, a lot of potentially hazardous situationswill have to be dealt with before the code can compile. When you impose strong typing as a way to writecode, Visual Basic as a programming language becomes in this sense very similar to other strongly typedlanguages like C# and Java.

The following are some of the benefits of activating this option:

❑ Visual Studio IntelliSense and Dynamic Help are activated. Visual Studio is not capable of pro-viding these two very productive features unless variable type is explicitly declared.

❑ Code readability is improved by variable type information. This can help you think about yourcode in the form of known abstractions and entities.

❑ Unwanted type coercion leading to precision loss, rounding errors, and possible overflow errorsis dealt with early. All implicit narrowing conversions are disallowed.

❑ Some performance gains result from function-call resolution being limited to compile time.

So how do you go about activating this option? While setting Option Explicit On was relatively easy,because we could count on the compiler to find all variables not declared explicitly and then just add themissing declaration statement, deciding on type is not that straightforward.

You start out by using the compiler to discover all variables that are declared without the type. Once youplace the Option Strict On statement at the beginning of the file, an error window will display thefollowing message for every variable for which type information is missing: “Option Strict On requiresall variable declarations to have an ‘As’ clause.”

A Slightly Artificial Example of Permissive VB codeBefore you go on with the task of converting the code into its strict form, you need to take a look at pos-sible behaviors of Visual Basic code when dealing with code written with Option Strict set to Off.You need to have a good understanding of such code in order to be able to convert it to strict form with-out altering the code’s behavior. You’ll see the convoluted use of variables, the implicit type conversions,and the problems that can crop up as a result of such code. Unless you understand well what is going onin the VB code written in relaxed form, you can unwittingly introduce bugs and errors into the code. Foran illustration of the behavior of permissive code, take a look at the code snippet in Listing 5-2.

Listing 5-2: An Example of Code with Option Strict Set to Off

Option Strict Off

Module Module1Sub Main()

‘ Initial type Integer (Int32)Dim variableX = 10 Console.WriteLine(“Type: “ & _variableX.GetType.Name.ToString)

‘Current type Long (Int64)variableX = 100000000000000000 Console.WriteLine(“Type: “ & _

116

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 116

Page 152: Professional Refactoring in Visual Basic

Listing 5-2: An Example of Code with Option Strict Set to Off (continued)

variableX.GetType.Name.ToString)

‘Current type DoublevariableX = 11.11 Console.WriteLine(“Type: “ & _variableX.GetType.Name.ToString)

‘Current type StringvariableX = “” & variableX Console.WriteLine(“Type: “ & _variableX.GetType.Name.ToString)

‘Late bound callConsole.WriteLine(variableX.Length)

Console.ReadLine()End Sub

End Module

Note the Option Strict Off statement at the beginning of the listing. Now, this code will demon-strate a radically different behavior of VB runtime under relaxed mode, when Option Strict andExplicit are set to Off. After you run this code, the following output appears in the console window:

Type: Int32Type: Int64Type: DoubleType: String5

Dynamically Changing the Underlying Variable Type What you can see here is that the underlying variable type has been changing dynamically. Or, to be more precise, the object at which variableX points has been changed. First, the variable is Integer, afterassigning it a literal value of 10. After the addition of a huge number, the type is changed to Long, in orderto accommodate the result of this addition. Next, after the addition of the decimal value 11.11, the type isagain changed in order to preserve the decimal part of the number. You can see that VB runtime is perform-ing the necessary operations and accommodating its behavior so it best suits the code. Finally, after the con-catenation of an empty string with variableX, the variable type is changed for the last time, to String.

“Under the hood,” each time a variable type is changed, a new object is created and the old one leftunreachable, waiting for garbage collection.

Late Bound CallsThe last line in the console output has the value 5, representing the string length. The length of the string is 5 because the Double is represented in its exponential form, as 1E+17. What is significant about this prop-erty call is the fact that you have not defined the variable as String. This is not something that the compilercan know at compile time. However, it assumes during execution that variableX will have the propertyLength. That’s why this call is late bound, which means that if the object referenced by variableX has somemember called Length, the call will succeed, and if the object does not have such a member, a runtime errorwill occur. Since variableX is pointing to the String object once you reach the line where the Lengthproperty is called (and String, of course, has a property named Length), the code executes successfully.

117

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 117

Page 153: Professional Refactoring in Visual Basic

This is possible because a lot of work that is normally done at compile time is delayed until the programis run and performed at runtime. What we are seeing in this sample is essentially a dynamically typedproperty of Visual Basic. However, once Option Strict is set to On, Visual Basic behaves just like anyother statically typed language, and calling the Length property on variableX without declaring it as aString is no longer possible.

I am now going to set Option Strict to On and perform all the necessary modifications to the samplecode so that it can compile and execute properly. While this example is very simple, it serves to illustratesituations you might encounter in real production code.

First Attempt at Inferring a Variable’s Initial TypeThe first error that the compiler reports once Option Strict is set to On is telling you that you need toassign a type to variableX. Here is the message: “Option Strict On requires all variable declarations

Smell: Implicit Type DeclarationDetecting the SmellAfter resolving the Implicit Variable Declaration, use the compiler to detect variables andmembers that are declared without their types being specified. Place Option Strict Onat the top of the file, compile the project, and look for the following errors in the Errorwindow.

❑ “Option Strict On requires all variable declarations to have an ‘As’ clause.”

❑ “Option Strict On requires all Function, Property, and Operator declara-tions to have an ‘As’ clause.”

❑ “Option Strict On prohibits operands of type Object for operator ‘-‘ [or other operator].”

❑ “Option Strict On disallows late binding.”

The IDE will also underline the identifier(s) in the Editor window corresponding to thepreceding error.

Related RefactoringUse Set Option Strict On refactoring to eliminate this smell.

RationaleFailing to declare variable type explicitly means that you will not be able to exploitsome very productive Visual Studio features, namely IntelliSense and Dynamic Help.Code in which variable and member type are declared explicitly outperforms code inwhich type is resolved at runtime. Code readability benefits from explicit type declara-tion. Ultimately, variable and member type being declared explicitly facilitates struc-turing and organizing your code by the means of standard object-oriented constructslike classes, interfaces, inheritance, and so on.

118

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 118

Page 154: Professional Refactoring in Visual Basic

to have an ‘As’ clause.” In this case, based on first literal value in the code, you can declare variableXas Integer. The declaration line now looks like this:

Dim variableX As Integer = 10

After this is done, you receive the following error: “Option Strict On disallows implicit conversions from‘Long’ to ‘Integer.’” It marks the following line of code:

variableX = 100000000000000000

This huge literal value is of type Long, and the compiler refuses to assign it to variableX of typeInteger because Integer is not capable of keeping such a huge value. At this point the IDE offers to resolve this problem by enclosing the number inside the CInt function. However, you cannot justsqueeze this number into the Integer. So either you go back and declare variableX as Long, or youintroduce a new variable.

Declaring variableX as Long will work in this case. However, in some other, albeit rare, cases, in whichreflection is used and execution logic depends on underlying variable type, this might introduce a bug. I am referring to cases in which code similar to the following line is present:

If TypeOf variableX Is Integer Then

So, in this case, I decide to play it completely safe. The next section shows how.

Convoluted Use of Variables Resolved by the Definition of New Variables

In cases where the variable has been overused and can represent different things depending on the con-text, a new variable should be introduced to compile the code in a strictly typed environment. By goingback to the example I will show you how this can work out in practice.

Introducing a New Variable for a New Type: the LongInstead of changing the variableX’s type to Long, I will declare a new variable. As you will see later on in the book, the fact that a variable’s type is changed can often be a sign that the variable is being overusedand is probably the result of diverse roles being given to that variable. This can be very confusing for thecode reader. Misuse of a local variable can go hand in hand with the Long Method smell. You have alreadyseen an example of the Long Method smell in Chapter 2, and it is described in detail in Chapter 9.

I will use Refactor!’s Split Temporary Variable refactoring to perform this transformation. I will nameour new variable variableXLong. Take a look at the Visual Studio IDE and how you can activate thisoption of the Refactor! add-in. Using the Split Temporary Variable refactoring by the Refactor! add-in tointroduce a new variable for a new type is shown in Figure 5-1.

Here I have used Split Temporary Variable refactoring in the context of converting the permissive codeto strict code. Split Temporary Variable refactoring is also refactoring in its own right, and I’ll deal withit later on in Chapter 10.

119

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 119

Page 155: Professional Refactoring in Visual Basic

Figure 5-1

The line I have changed now looks like this:

Dim variableXLong As Long = 100000000000000000

Also, in all subsequent lines, Refactor! has replaced variableX with variableXLong. I have movedanother step forward in introducing Option Strict.

Dealing with the DoubleNext on the list to deal with is the following line:

variableXLong = 11.11

Here the following error is reported: “Option Strict On disallows implicit conversions from ‘Double’ to‘Long.’” Again, IDE is offering the following solution: “Replace 11.11 with CLng(11.11).” While this solutioneliminates the compilation error, it introduces a bug whereby the decimal part of the number is simply lost. I am trying here to preserve the original behavior of the code, so once again I do not trust the IDE. Yet again,I’ll introduce another new variable. The name of the variable this time is variableXDouble and its type isDouble. Refactor! replaces all instances of variableXLong with variableXDouble for the rest of the codein the method.

That’s one error more eliminated, and one more to go.

120

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 120

Page 156: Professional Refactoring in Visual Basic

Dealing with the StringNow I can deal with the remaining error. At the following line:

variableXDouble = “” & variableXDouble

I am receiving the following error: “Option Strict On disallows implicit conversions from ‘String’ to‘Double.” The solution is the same: I’ll introduce another variable called variableXStr. However, thistime the transformation has to be performed manually, because I have reached the last assignment tovariableXDouble. The offending line now looks like this:

Dim variableXStr As String = “” & variableXDouble

As another consequence, the error has disappeared from the following line:

Console.WriteLine(variableXStr.Length)

variableXStr is declared as a String, so the compiler can check that this type contains the property Length.

Listing 5-3 shows the complete sample so you can see the final result.

Listing 5-3: Sample Code After it Has Been Modified So That Option Strict is Activatedand the Code Converted to Strict Form

Option Strict On

Module Module1Sub Main()

Dim variableX As Integer = 10Console.WriteLine(“Type: “ & _variableX.GetType.Name.ToString)

Dim variableXLong As Long = 100000000000000000Console.WriteLine(“Type: “ & _variableXLong.GetType.Name.ToString)

Dim variableXDouble As Double = 11.11Console.WriteLine(“Type: “ & _variableXDouble.GetType.Name.ToString)

Dim variableXStr As String = “” & variableXDoubleConsole.WriteLine(“Type: “ & _variableXStr.GetType.Name.ToString)

Console.WriteLine(variableXStr.Length)

Console.ReadLine()End Sub

End Module

121

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 121

Page 157: Professional Refactoring in Visual Basic

Finally, after we execute the code, the resulting Console output is identical to the output from the beginning:

Type: Int32Type: Int64Type: DoubleType: String5

In converting this code to statically typed, I used a very simple methodology. I started assigning types to variables based on the initial type and literal values. Once this type was changed, I introduced a newvariable to hold this new value. Now I want to look at the other ways you can discover the initial typefor the variable.

Inferring Variable TypeWhen you are confronted with the permissive code, the underlying variable type is not always obvious.You saw an attempt to infer variable type in the sample code in the previous section. Now I want to inves-tigate different techniques you can apply in order to infer the type of the variables used in relaxed code.

Using Literals to Infer Variable TypeFirst on the list are variables that have literal values assigned to them. The types of these variables are relatively easy to decipher; take a look at Table 5-2 for some samples. I have marked with bold the typicalchoice that will work on most occasions. You should make sure to take the type range into account whendeciding on the right type.

Table 5-2: Different Forms of Literals Usage and Type Inference

Declaration without the Type Complete Variable Declaration

Dim name = “John” Dim name As String = “John”

Dim year = 1997 Dim year As Byte = 1997Dim year As Short = 1997Dim year As Int16 = 1997Dim year As UInt16 = 1997Dim year As Integer = 1997Dim year As Int32 = 1997Dim year As UInt32 = 1997Dim year As Long = 1997Dim year As Int64 = 1997Dim year As UInt64 = 1997

Dim price = 185.56 Dim price As Single = 185.56Dim price As Double = 185.56

Dim price = 185.56D Dim price As Decimal = 185.56D

Dim initial = “G”c Dim initial As Char = “G”c

Dim success = True Dim success As Boolean = True

122

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 122

Page 158: Professional Refactoring in Visual Basic

You may notice that in some cases more than one applicable type is available. How do you decidewhich one to use? The solution is to take type range into account. Generally, you are a lot less likely togo wrong if you choose a type with a wider range. For example, if you try to declare the year as Byte,you will receive a compiler error stating “Constant expression not representable in type Byte.” This isbecause the Byte type can only represent values between 0 and 255. While you might choose to repre-sent the variable year with the Single type, because it is not likely that it will come out of range whenrepresenting the year, you can’t always make such assumptions. Another danger involved in makingassumptions like this, as I already mentioned, is winding up with code that uses type information aspart of execution logic.

If you are in doubt about the range of different numeric types in VB, this is a good moment to consult MSDN. You can take a look at the Visual Basic Data Type Summary at the following URL:http://msdn2.microsoft.com/en-us/library/47zceaw7(VS.90).aspx.

In case a variable range is not sufficient to contain a certain value, an Overflow exception is thrown atruntime. If this error is not dealt with, the application crashes. When Option Strict is set to Off and a variable is initialized with a whole-number literal, the type used is Int32. This, of course, is only if thenumber in question can be contained in Int32. For a very big literal value, out of the range of Int32, anInt64 type is used. Unless you are working on some very memory-intensive application, there is noneed to reduce the range and declare such variables as Short or Int16.

Next, I want to examine another very simple way to infer initial variable type.

Using the Conversion Function to Infer Initial Variable TypeVisual Basic has a number of predefined conversion functions. These functions accept a single value and return a value converted to another type. Table 5-3 examines those functions and the types they return.

Table 5-3: Types Returned by Conversion Functions

Continued

Function Function Return Type

CBool Boolean

CByte Byte

CChar Char

CDate Date

CDbl Double

CDec Decimal

CInt Integer

CLng Long

123

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 123

Page 159: Professional Refactoring in Visual Basic

Table 5-3: Types Returned by Conversion Functions (continued)

This is really quite straightforward. Take a look at the following line:

Dim variableX = CLng(variableY)

According to the table, this line is transformed into the following once Option Strict is set to On:

Dim variableX As Long = CLng(variableY)

Another function, similar to one you have just seen in action, is CType. This function accepts twoparameters — the first is the variable, and the second is the targeted type. This makes the return typequite obvious: it is stated as a second parameter in the function call. The code:

Dim variableXvariableX = CType(variableY, SqlConnection)

is modified into this:

Dim variableX as SqlConnectionvariableX = CType(variableY, SqlConnection)

For one final way to infer variable type, I want to take a look at external assemblies and the way thoseassemblies are consumed.

Using External Assemblies to Infer Initial Variable TypeWhen you use external assemblies, you create objects and receive objects as the return values of methodsand properties. Thanks to .NET’s strongly typed nature, these assemblies are strongly typed. Therefore,the type is easily inferred. Consider this example:

Dim Conn = New SqlConnectionDim Cmd = Conn.CreateCommand()

Function Function Return Type

CObj Object

CSByte SByte

CShort Short

CSng Single

CStr String

CUInt UInteger

CULng ULong

CUShort UShort

124

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 124

Page 160: Professional Refactoring in Visual Basic

Moving to the strict mode modifies this code as follows:

Dim Conn As SqlConnection = New SqlConnectionDim Cmd As SqlCommand = Conn.CreateCommand()

However, in most cases you should strive not to depend on specific implementation, but on moregeneric constructs. Often interfaces are used to separate abstraction from implementation. In this spe-cific case SqlConnection and SqlCommand are types belonging to a specific implementation of theADO .NET data provider for MS SQL Server. If your code is not using any other specific membersbelonging to SqlConnection or SqlCommand, and all invoked members are also defined in interfacesthat these two classes implement, you are better off declaring your variables by using the correspon-ding interfaces as a type. The code will then look like this:

Dim Conn As IDbConnection = New SqlConnectionDim Cmd As IDbCommand = Conn.CreateCommand()

Now, in case you need to replace the data provider because you are using a different data store, youneed to modify only the single line where the connection is created. The rest of the code is guaranteed to work with any ADO .NET data provider.

Putting It All Together with Type-Conversion FunctionsAfter you have done your best to infer all variable types, you might still end up with some problemsstemming from implicit type conversion. After Option Strict is set to On, only widening conversionsare allowed in implicit form, and any narrowing conversion has to be explicitly written in code.

Smell: Implicit Narrowing ConversionsDetecting the SmellUse the compiler to detect implicit narrowing conversions. After resolving the ImplicitType Declaration smell, place Option Strict On at the top of the file, compile theproject, and look for the “Option Strict On disallows implicit conversions from ‘typeX‘to ‘typeY‘” error in the Error window. The IDE will also underline the identifier in theEditor window if an undeclared variable is present.

Related RefactoringUse Set Option Strict On refactoring to eliminate this smell.

RationaleIf narrowing conversions are allowed and performed quietly, a number of bugs canarise as a result of narrowing and rounding errors. For example, the following lineswill compile under Option Strict Off:

Dim varX As Double = 1234.99Dim varY As Long = varXConsole.WriteLine(varY)

However, the output will show 1235 because of rounding during the transformation ofthe number into an integer.

125

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 125

Page 161: Professional Refactoring in Visual Basic

You have already seen predefined conversion functions and how they can be used to infer initial variabletype. Now they can come in handy to perform explicit conversion. I want to illustrate that with somecode. The following snippet compiles with Option Strict set to Off:

Option Strict Off‘...Dim varX = 123Dim varY = “456”Dim varZ = 123456.789varX = varZDim result = varX + varYDim varLong As Long = varX

However, after you set Option Strict to On and declare all variable types, you still receive a few errors:

Option Strict On‘... Dim varX As Integer = 123Dim varY As String = “456”Dim varZ As Double = 123456.789 ‘Implicit conversion from Double to Integer not allowedvarX = varZ ‘Implicit conversion from String to Integer not allowedDim result As Integer = varX + varY ‘Widening conversion from Integer to Long is allowedDim varLong As Long = varX

In order to resolve these errors, you use conversion functions to perform conversions explicitly. Inthis case, for the sake of argument, consider that you are allowed to ignore the decimal part of varZ.You can use CInt to convert both varZ and varY to Integer. The final, compilable version looks like this:

Dim varX As Integer = 123Dim varY As String = “456”Dim varZ As Double = 123456.789varX = CInt(varZ) ‘CInt conversion function at workDim result As Integer = varX + CInt(varY) Dim varLong As Long = varX

Explicit Variable-Type Conversion with CTypeI have already mentioned the CType function, which can perform all the conversions that other built-in conversion functions like CStr and CInt can perform. It actually makes little difference whether you useCInt(varX) or CType(varX, Integer). However, CType can actually do a bit more. It enables you to con-vert between any two types of objects, if such a conversion is defined by means of a conversion operator.This is accomplished through the operator-overloading feature of Visual Basic. The typical use of CType instrictly typed code is to retrieve an instance from a nongeneric collection. (Standard, nongeneric collectionsare found in the System.Collections namespace.) Because a collection always returns an object, you can

126

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 126

Page 162: Professional Refactoring in Visual Basic

use CType to cast an instance into the correct type. For example, in the following snippet I cast an objectobtained from ArrayList into the Client type:

Dim clients As System.Collections.ArrayList‘...Dim client As Client = CType(clients.Item(id), Client)

Explicit Variable-Type Conversion with DirectCast and TryCastTwo other interesting functions from this group of type-conversion functions are DirectCast and TryCast.They perform identically, except upon failure at runtime. In case of a conversion error, DirectCast throwsInvalidCastException while TryCast will return nothing.

Basically, by using DirectCast or TryCast you can accomplish a downcast. A downcast is a conversionbased on inheritance or interface implementation. Remember the classes Patient, MalePatient, andFemalePatient from Chapter 2? Both MalePatient and FemalePatient inherited Patient. Again,take a look at the code sample:

Dim patient As Patient = GetMalePatient()Dim malePatient As MalePatient = patient

This code provokes a compiler to report an error when Option Strict is turned on. However, if youare certain that the patient variable points to an instance of MalePatient, you can write the codewithout inciting a compiler error, like this:

Dim patient As Patient = GetMalePatient()Dim malePatient As MalePatient = DirectCast(patient, MalePatient)

For this code to work, MalePatient must inherit the Patient class, or implement the Patient interface —if Patient was defined as an interface.

It is worth mentioning that the upcasting is performed implicitly, so the following code will work withoutany need for DirectCast:

Dim malePatient As MalePatient = GetMalePatient()Dim patient As Patient = patient

Now you have investigated issues of type related to local variables and, more importantly, becomefamiliar with various ways to move your use of variable type from implicit to explicit to create strictercode. Of course, type doesn’t relate only to variables, so now as you move on in the chapter you will seehow all this relates to other typed class elements.

Dealing with Methods, Fields, Properties, and Other MembersSo far, most of the samples have used local variables to illustrate type inference. While all the describedtechniques apply to class and instance members, the situation can get a bit more complicated whenyou’re dealing with properties, methods, and events.

Visual Basic with Option Strict set to Off enables you to write methods without having to specifyparameter types or the return type of the method. After using all the previously described techniques to decipher types in a method signature, you can still end up in a situation where the solution requiressome radical changes to the way a method is written. To show some typical cases, I’ll start off with thesample code in Listing 5-4.

127

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 127

Page 163: Professional Refactoring in Visual Basic

Listing 5-4: A Method with Ambiguous Parameter Types

Public Function NumberOfRecords(ByVal data, ByVal dataFormat)If dataFormat = “xmlNode” Then

Return data.ChildNodes.CountElseIf dataFormat = “dataTable” Then

Return data.Rows.CountEnd If

End Function

In this case the parameter data can be of the System.Xml.XmlNode or System.Data.DataTable datatype, depending really on what the client code sends to the method. Another parameter, dataFormat,indicates the type of parameter, so based on that additional information, the correct branch of code isexecuted and desired result obtained.

Overloading Methods to Resolve an Ambiguous Parameter-Type SituationYou can start out by inferring the return type of the method. In order to do that, you inspect the signatureof the Count property of the ChildNodes property of XmlNode. Next, you inspect the Count property ofthe Rows property of DataTable. You can do this easily with the Object Browser. In both cases the type is the same — Integer. Now you need to decide on dataFormat’s type. It’s easy to do this based oncomparisons of the dataFormat parameter with string literals. So far so good — the method’s signatureshould look like this:

Public Function NumberOfRecords(ByVal data, _ByVal dataFormat As String) As Integer

All that is left to do is to decipher the type of the first parameter. As I already said, this parameter can beeither XmlNode or DataTable. To determine which, you can divide this method into two new overloadedmethods. One contains code applicable to XmlNode, and the other code applicable to DataTable. (Theprocess of splitting the method into two is explained in detail when we look at Extract Method refactoringin Chapter 9.) After you do that, you end up with the code shown in Listing 5-5.

Listing 5-5: A Method with Ambiguous Parameter Types Split into Two Overloaded Methods

Public Function NumberOfRecords(ByVal data As DataTable, _ByVal dataFormat As String) As Integer

Return data.Rows.CountEnd Function

Public Function NumberOfRecords(ByVal data As XmlNode, _ByVal dataFormat As String) As Integer

Return data.ChildNodes.CountEnd Function

This way, once the client code is strongly typed, the compiler will use information on the data parametertype to dispatch the call to the correct overloaded version of the method. Going one step further, you cannow notice that the dataFormat parameter has no use for us anymore. If you have access to all clientcode you can simply eliminate this parameter. If you do not have access to all client code you can main-tain methods with the dataFormat parameter, but you can also mark them with the Obsolete attributeand redirect the call to the overloaded versions of the method without the dataFormat parameter. Theresulting code is shown in Listing 5-6.

128

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 128

Page 164: Professional Refactoring in Visual Basic

Listing 5-6: Overloaded and Obsolete Methods as Solution to Ambiguous Parameter Types Method

<Obsolete(“dataFormat parameter not needed”)> _Public Function NumberOfRecords(ByVal data As DataTable, _ByVal dataFormat As String) As Integer

Return NumberOfRecords(data)End Function

<Obsolete(“dataFormat parameter not needed”)> _Public Function NumberOfRecords(ByVal data As XmlNode, _ByVal dataFormat As String) As Integer

Return NumberOfRecords(data)End Function

Public Function NumberOfRecords(ByVal data As DataTable) As IntegerReturn data.Rows.Count

End Function

Public Function NumberOfRecords(ByVal data As XmlNode) As IntegerReturn data.ChildNodes.Count

End Function

This takes care of resolving a situation in which parameter type is ambiguous, but the method returntype can be ambiguous also. The next section shows how to act in that case.

Introducing New Methods to Resolve an Ambiguous Return TypeVisual Basic will not let you overload methods based solely on the return type. In cases where a methodreturns different types, you cannot use method overloading as a solution. Check out the followingmethod, which has an ambiguous return type:

Public Function GetData(ByVal store)If store = “Xml” Then

Dim node As XmlNodenode = GetNode()Return XmlNode

ElseIf store = “ADO” ThenDim table As DataTabletable = GetTable()Return table

End IfEnd Function

The solution in this case is again to first split this method into two. One method will return XmlNode andthe other DataTable. You can take a look at the related Extract Method refactoring in Chapter 9 in orderto understand the mechanics in detail, but in this case each method has to have a different name. Also,once you split GetData method into two, the store parameter becomes redundant. You end up withcode looking like this:

Public Function GetDataAsXmlNode() As XmlNodeDim node As XmlNodenode = GetNode()

129

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 129

Page 165: Professional Refactoring in Visual Basic

Return nodeEnd Function

Public Function GetDataAsDataTable() As DataTableDim table As DataTabletable = GetTable()Return table

End Function

If you have access to all client code, you can replace all calls to GetData with calls to GetDataAsXmlNodeor GetDataAsDataTable, depending on the type that the client code expects. With that, you have resolvedthe issue.

In case you do not have access to all client code, you can keep the original GetData method. You modifythe method so it delegates the call to the new methods GetDataAsXmlNode and GetDataAsDataTable.

The last issue is deciding on the return type of this method. Because it is now in a statically typed environ-ment, you need to add an As clause to the GetData method declaration. The problem is that in one case itreturns XmlNode and in another DataTable. So you can opt for the common base type of XmlNode andDataTable. Because in .NET everything derives from System.Object, you can make this method returnObject. And you can infer that the store parameter type is String. The final version of the GetDatamethod is shown in Listing 5-7.

Listing 5-7: A Method with an Ambiguous Return Type is Marked with the ObsoleteAttribute and Returns an Object for Compatibility Purposes

<Obsolete(“Use GetDataAsXmlNode or “ & _“GetDataAsDataTable instead”)> _Public Function GetData(ByVal store As String) As Object

If store = “Xml” ThenReturn GetDataAsXmlNode()

ElseIf store = “ADO” ThenReturn GetDataAsDataTable()

End IfEnd Function

This way you have resolved a situation in which a method can dynamically change its return type.

A similar approach to the one we just used in dealing with methods can be applied to properties andevents. With properties, overloading can be applied only in cases of parameterized properties. However,parameterized properties are a lot less common, so if a property is ambiguous in relation to its type, yougenerally end up splitting properties into two.

Of course, this section does not cover all possible situations you can come upon. Some situations willrequire more inventiveness. However, the rule of thumb is to start by resolving the simplest situations,those in which type can be inferred by literal assignment, other assembly, or conversion function. Thatway you scale down your problem and solution to more problematic situations that then become evi-dent. In my experience, it seems that in most cases programmers do declare the variable type, and afterOption Strict has been set to On, only a small number of undeclared type variables will crop up.

130

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 130

Page 166: Professional Refactoring in Visual Basic

Continued

Refactoring: Set Option Strict On (Enforce Variable Type Declaration and Explicit Type Conversions)

MotivationVisual Studio IntelliSense and Dynamic Help can be activated only if the variable andmember type are explicitly declared in the declaration by the means of an As clause.After Option Strict On is activated, the compiler prevents all implicit narrowingtype conversions. This means that all narrowing and rounding has to be performeddeliberately, by the programmer, by means of conversion functions. This means that a number of bugs resulting from rounding and narrowing errors can be avoided. Byexplicitly declaring types you improve code readability, because you can think in theform of known abstractions and types. Finally, declaring types explicitly leads to someperformance gains as a result of early binding.

Once Option Strict is set to On, Visual Basic behaves as a statically typed language,just like C# or Java, and similar styles and idioms can be applied.

Related SmellsUse this refactoring to eliminate Implicit Type Declaration and Implicit NarrowingConversions smells.

MechanicsThe Option Strict statement is module-level only. Option statements have to be thefirst statements in the file. Set Option Explicit On refactoring should precede thisrefactoring. Start by inferring and declaring types explicitly for local variables. Firstdeal with the simplest situations, those in which type can be inferred by:

❑ Literal value assignments

❑ Using types from other assemblies

❑ Using conversion functions to decipher variable type

Introduce new local variables to resolve situations in which variable type is ambigu-ous, where one variable’s underlying type is changed during the variable lifetime.After resolving local variable type, declare class and instance member types. Since typeinformation for the local variable is now available, you have more information to go byin order to infer typed member declaration.

Overload methods in order to resolve ambiguous method-parameter situations.

Split methods into two in order to accommodate situations involving an ambiguousmethod return type.

Use the Obsolete attribute to mark method versions for which the Object type isused, in order to maintain client compatibility. The Obsolete method should only del-egate calls to strongly typed method versions.

Finally, use a conversion function to explicitly convert between types. Explicit conver-sion is required for narrowing conversions only.

131

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 131

Page 167: Professional Refactoring in Visual Basic

Make Explicit Refactoring in Refactor!If you are using Visual Studio 2008, you will be able to use Refactor! to automate the refactoring I justdescribed. Refactor! can infer type and add the As part of the declaration statement for any variable thathas been initialized with the value. This refactoring can be applied to both local, method-level variablesand fields.

In order to activate it, place the cursor on the variable name in the variable declaration statement with-out the As clause. Take a look at Figure 5-2 to see this refactoring in action.

Applying Set Option Strict On Refactoring to the Rent-a-Wheels Application

Now it’s time to see how this refactoring can apply to the Rent-a-Wheels application. I’ll stick withBtnDelete_Click of the FrmBranch class, the method I used as an example earlier in the chapter(Listing 5-1, when Set Option Explicit On refactoring was introduced). You can turn back in order to refresh your memory about this method and the form it took after Option Explicitwas introduced.

BeforeOption Explicit On ‘...Public Function BasketTotal()

Dim total Dim item For Each item In Basket

total += item.PriceNextBasketLog.WriteEntry(total, _EventLogEntryType.Information, myeventid)Return total

End Function

AfterOption Explicit On‘Option Strict On statement added Option Strict On‘ ...‘Declare method return type Public Function BasketTotal() As Double

‘Declare local variable typeDim total As DoubleDim item As ProductFor Each item In Basket

total += item.PriceNext‘Explicit conversion through CStr conversion functionBasketLog.WriteEntry(CStr(total), _ EventLogEntryType.Information, myeventid)Return total

End Function

132

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 132

Page 168: Professional Refactoring in Visual Basic

Figure 5-2

While the code seemed to promise an easy time when it came to activating Option Strict, a surprise waswaiting for me. Because I wanted to keep the code as data-store-neutral as possible, I chose to declare oCnas IDbConnection from the System.Data namespace, hence limiting the dependency on the MicrosoftSQL data provider to Connection and Command creation code. So far, so good. However, once I tried todeclare oCmd as IDbCommand, the compiler reported the following error: “‘AddWithValue’ is not a memberof ‘System.Data.IDataParameterCollection.’”

The class that implements a certain interface has to implement all the members declared in that inter-face, but it is free to declare additional members. In this case, the AddWithValue method is a member ofSystem.Data.SqlClient.SqlCommand, but it is not a member of System.Data.IDbCommand. It seemsthat Microsoft provided a convenient way to add command parameters, but it is exposed only if youwork directly with the SqlCommand type. So I need another way to add parameters to a command objectin a provider-neutral way.

So the code is using some MS SQL provider-specific methods. While adding the parameter to a commandthis way takes a single statement, thanks to a convenient AddWithValue method, depending on a specificprovider implementation this way can soon grow out of proportion and can prevent you from making yourcode data-agnostic. I decided to try to modify the code so that it uses IDbCommand methods only. This meansadding a few lines and using the IDbCommand.CreateParamter and IDbCommand.Parameters.Addmethods. Finally, I ended up with code listed in Listing 5-8.

Listing 5-8: The FrmBranch Class BtnDelete_Click Method from the Rent-a-WheelsApplication, After Option Strict is Set to On

Private Sub BtnDelete_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnDelete.Click

‘oCn declared as IDbConnection

133

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 133

Page 169: Professional Refactoring in Visual Basic

Listing 5-8: The FrmBranch Class BtnDelete_Click Method from the Rent-a-WheelsApplication, After Option Strict is Set to On (continued)

Dim oCn As IDbConnection ‘ oCmd declared as IDbCommandDim oCmd As IDbCommand On Error GoTo ErrorHandler‘Create SqlConnectionoCn = New SqlConnection(“Data Source=TESLA-DAN;” + _“Initial Catalog=RENTAWHEELS;User ID=sa”)oCmd = New SqlCommand‘strSql declared as StringDim strSql As String = “Delete Branch “ + _

“Where BranchId = @Id”‘Adding command paramter in provider-neutral mannerDim Id As IDbDataParameter = oCmd.CreateParameter() Id.ParameterName = “@Id” Id.DbType = DbType.Int32 Id.Value = CInt(TxtId.Text) oCmd.Parameters.Add(Id) ‘open connectionoCn.Open()‘Set connection to commandoCmd.Connection = oCn‘set Sql string to command objectoCmd.CommandText = strSql‘execute commandoCmd.ExecuteNonQuery()‘close connectionoCn.Close()‘destroy objectsoCmd = NothingoCn = NothingFrmBranch_Load(Nothing, Nothing)Exit Sub

ErrorHandler:MsgBox(“A problem occurred and the application can not recover! “ + _

“Please contact the technical support.”)Err.Clear()

End Sub

This code is actually longer than the previous version. While this is not a good development, you oftenneed to weigh pros and cons. In this case I think it is more important to keep the application databaseneutral. Also, I have the feeling I’ll find a way to write this code more efficiently once I have the applica-tion well through the refactoring process.

Now that you have looked into converting code written without type declarations and conversions intocode for which types and conversions are declared explicitly, I want to take a look at some other interestingVisual Basic characteristics related to the problems of strong static code versus weak dynamic code. In nextsection I cover some situations where it can actually be beneficial to write code without declaring variableand member type.

134

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 134

Page 170: Professional Refactoring in Visual Basic

Static Versus Dynamic Typing and Visual BasicIn Visual Basic .NET you can achieve late binding by combining the use of the Option Strict Offstatement with declaring variables as Object. Essentially, late binding means that variable type is notknown at compile time. This results in type checking being performed at runtime, which is why latebinding is also often referred to as dynamic typing. Because the type checking is limited to verifying thata certain class member is present in a late bound object, you can compile the code that will produce atype-related error at runtime. Take a look at the example in Listing 5-9.

Listing 5-9: In Dynamically Typed Code Certain Errors Can Be Discovered Only at Runtime

‘ Static type checking deactivatedOption Strict Off #1

Module Module1Sub Main()

Dim engine As Engine = New EngineDim tree As Object = New Treeengine.Start()‘Calling Start method on Tree instancetree.Start()

End SubEnd Module

Public Class EnginePublic Sub Start()

‘...End Sub

End Class

‘Tree class does not have Start memberPublic Class Tree

Public Sub Grow()‘...

End SubEnd Class

You can compile this code successfully, but once you try to run it a MissingMemberException will bethrown. Because the Tree class does not have a Start method, the last line in Main is the source of an error.

If you now activate the Strict option (replace Option Strict Off with Option Strict On), youget a compile-time error with the following message: “Option Strict On disallows late binding.” You areno longer able to compile such code.

This can be a great help in preventing typos and similar errors. Just imagine that I have writtenengine.Stat() by mistake. The compiler is now helping me identify the error right away, duringcompilation: “Stat is not member of Engine.” And because IntelliSense is also present, there is only aslim chance I’ll lose too much time on this type of error.

However, traditional Visual Basic programmers have a lot of experience in using late binding, andthere is a reason for that. To get some historic perspective on late binding, you need to look at its rolein Visual Basic 6.

135

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 135

Page 171: Professional Refactoring in Visual Basic

Late Binding in Visual Basic 6 and PriorIn the COM era (Visual Basic 6 and prior), late binding often presented a workaround for COM-versioningand binary-compatibility issues. When performing early binding in referencing COM components, VisualBasic would bind to a specific version of a component. In COM, a different set of GUIDs was used to iden-tify a component. Each time a component interface was changed, meaning that a signature of any non-private member, a method, or a property was changed, a completely new GUID for that component wascreated. This version of the component had no relation to the previous version of that same component.This means that if an application was performing early binding, any other binary incompatible version ofthe component would provoke the infamous “Error 429 — ActiveX component can’t create object.”

In the case of late binding, as long as a new version of a component had all the same methods that theprevious version had, even when containing new non-private methods the application would continueto work. For this to be accomplished, a variable had to be declared as Object and created using theCreateObject function with ProgId as a parameter. That meant that Visual Basic would check forthe existence of a method or a property only at runtime, during the execution. As long as the objectsupported the function or property in question, the code could execute without any problems.

Duck TypingThis style of programming, in which you rely on type being checked by member signature comparison at runtime, is also known as duck typing. The name originates from the saying “If it walks like a duck andquacks like a duck, it must be a duck.” As long as the object has the member that corresponds to what theclient code is expecting, everything works out fine. A number of modern languages, like Ruby and Python,and some older ones, like Smalltalk, permit this style of programming. Devotees of dynamically typed lan-guages emphasize the flexibility and productivity that can be obtained from this approach and praise itover the benefits that static type-checking can bring. This style generally works well when paired with unittesting that can create a much deeper safety net than static typing can. In dynamically typed languagesfunction calls are resolved at runtime, which permits treating objects in a polymorphic manner, but withoutany interface- or implementation-inheritance relationship between those objects.

A similar style is possible in VB .NET, but can be turned off at will by means of the Option Strict Onstatement. This option did not exist in pre-.NET versions of Visual Basic. This means that in pre-.NET ver-sions of VB, anything declared as Object is late bound. However, in VB .NET, once you place the OptionStrict On statement at the top of your file, you have effectively turned off late binding in that file.

Now take a look at a sample in which this style can come in handy. Consider the following snippet:

Option Strict Off‘...Public Sub CheckSpellingAndSave(ByVal officeObject As Object)

officeObject.CheckSpelling()officeObject.SaveAs(FileName)

End Sub

For this code to compile, you need to set Option Strict to Off. As long as the officeObject parameterwas created as Excel.Worksheet or Word.Document, this code executes fine thanks to the fact that bothof these objects have both the CheckSpelling and SaveAs methods.

136

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 136

Page 172: Professional Refactoring in Visual Basic

It is also possible to write this code with Option Strict on. However, as you’ll see, it is a lot more verbose — it requires you to write much more code to accomplish the same behavior, and it’s based on the reflective capabilities of Visual Basic. Again, take a look at the code:

Option Strict On‘...Public Sub CheckSpellingAndSave(ByVal officeObject As Object)

‘Object array of parameters is passed to Invoke method ‘of System.Reflection.MethodInfo classDim saveAsParams As Object() = {FileName} ‘Invoking method without parameters using reflectionofficeObject.GetType.GetMethod(“CheckSpelling”). _ Invoke(officeObject, Nothing) ‘Invoking method with parameter using reflectionofficeObject.GetType.GetMethod(“SaveAs”). _ Invoke(officeObject, saveAsParams)

End Sub

This code is based on reflection, and all that you can do with late binding in VB — and even more — youcan also do with reflection. However, the quantity of code written will be very different. In this case, thefirst sample has only two lines, but the second, reflection-based version is significantly more verbose. Nowimagine you have to write a lot of code similar to this. You will definitely wish to use the less prolix variant.

Finally, without using reflection, the only solution in strict mode is to have two overloaded methods:

Option Strict On‘...Public Sub CheckSpellingAndSave(ByVal officeObject As Worksheet)

officeObject.CheckSpelling()officeObject.SaveAs(FileName)

End Sub

Public Sub CheckSpellingAndSave(ByVal officeObject As Document)officeObject.CheckSpelling()officeObject.SaveAs(FileName)

End Sub

The problem with this solution is that you have identical code in both methods. It means that the code isduplicated. As you will see in Chapter 9, duplicated code is the number-one smell and has to be elimi-nated. So obviously, this solution does not make me happy either.

Now, in more a common situation you could resolve this problem by making both Document andWorksheet inherit the same base class or implement the same interface. As a matter of fact, ExtractInterface is a standard refactoring technique that I will discuss in Chapter 12. It means no duplicationand strict code at the same time.

However, in this case you do not have a source for the Office automation libraries, so working theproblem out this way is not possible. You cannot modify the Document or Worksheet class. You needto look for a different solution.

137

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 137

Page 173: Professional Refactoring in Visual Basic

Resetting Dynamic or Static Behavior at the File LevelAs I already mentioned, once Option Strict is activated, VB behaves as a completely staticallytyped language, on par with C# or Java. However, as a unique feature of VB compared to other staticor dynamic languages, VB lets you specify this option on the file level. This means that you can mixfiles written in static and in dynamic style in the same project. You have another level of flexibilitywhen you write code. The VB team from Microsoft sees this as a distinctive advantage of VB overstatic-only or dynamic-only languages, and some further enhancements of VB as a dynamically typedlanguage are planned for the next release of Visual Basic. Microsoft’s position on the issue is expressedin the following sentence by Erik Meijer and Peter Drayton: “Static typing where possible, dynamictyping when needed.”

Erik Meijer’s and Peter Drayton’s full paper “Static Typing Where Possible, Dynamic Typing When Needed: The End of the Cold War Between Programming Languages” can be accessed athttp://research.microsoft.com/~emeijer/Papers/RDL04Meijer.pdf.

This leads to an interesting way of looking at this problem. Maybe there is some way to isolate dynamicfrom static code? This way, you could keep code strict in all but a minimal part of the application.

But first, a few words about VB as a dynamic language. Some dynamic languages go further than VB withthe dynamic paradigm. In these languages not only do you not need to know the exact type at compile time,but you are also able to modify types by adding properties and methods “on the fly” at runtime. Somethingsimilar is not possible in VB without the use of services provided by the Reflection.Emit namespace. Soyou can be pretty sure that all types you are going to encounter existed at the moment of compiling the pro-gram. Even if some types are loaded dynamically, they were most probably compiled and created as statictypes. Consequently, there is actually a physical assembly or dll file containing these types. It is highly prob-able that these types were created intentionally to represent a certain defined entity, along usual OO designand analysis guidelines.

With all this in mind, the following conclusion can be reached: in VB, even when writing dynamic code,you are still interacting with static types underneath. Going back to the CheckSpellingAndSave codesnippet, you have seen that this code interacted with two types: Word.Document and Excel.Worksheetfrom the Office automation library. Knowing all this, it makes sense to provide a statically typed wrapperover the dynamic code. The rest of the code will never know that for the moment static type checking wasturned off. Client code will treat the wrapper just as it would any other static type and will never knowthat you have used it to encapsulate dynamic style implementation.

Providing a Statically Typed Wrapper for Dynamic CodeIn the sample you are dealing with two types, Document and Worksheet, and they are exhibiting somecommon behavior. In the sample those are the CheckSpelling and SaveAs methods. However, sincethey do not share any common type, except the ultimate base type Object common to any other type in .NET, you can not access the services these objects provide in a common way.

Now, if you had source code for these two classes, you could just declare an interface — for example,you could name it IOfficeWrapper — declare all common methods for Document and Worksheet inthat interface, and make Document and Worksheet implement this interface. This is typical ExtractInterface refactoring, and I’ll talk about it in Chapter 12. This way, Document and Worksheet can beseen through a common interface and treated uniformly. So you can start by doing just that, writing anIOfficeWrapper interface to contain all the methods used in a common way (see Listing 5-10).

138

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:07 AM Page 138

Page 174: Professional Refactoring in Visual Basic

Listing 5-10: A Statically Typed Interface for the Office Wrapper

Option Explicit On‘Static type checking turned onOption Strict On

Public Interface IOfficeWrapperSub CheckSpelling()Sub SaveAs(ByVal fileName As String)

End Interface

Since you can change neither the Excel.Worksheet nor the Word.Document class, you need to create anew class to implement this interface. This new class will delegate calls to the original officeObject, andto be able to treat both the Worksheet and Document objects in the same way it will be written in dynami-cally typed style. This means you need to place the class in a separate file and deactivate Option Strict.

You provide a wrapper with a constructor, so you can receive an officeObject and keep a reference tothe officeObject in a private field. The CheckSpelling and SaveAs methods will only delegate a call toa reference of the original officeObject maintained as a field in the OfficeWrapper class. Listing 5-11shows the resulting code.

Listing 5-11: A Dynamically Typed Class Wrapper for the Office Wrapper Implementingthe IOfficeWrapper Interface

Option Explicit On‘Note Option Strict deactivatedOption Strict Off

‘ Class is implementing IOfficeWrapper interfacePublic Class OfficeWrapper

Implements IOfficeWrapper ‘Private field maintains a reference to Document or Worksheet objectPrivate wordDocOrExcelWorksheet As Object

Public Sub New(ByRef wordDocOrExcelWorksheet As Object)‘Constructor accepts reference to Document or Worksheet instance ‘and keeps it in a private fieldMe.wordDocOrExcelWorksheet = wordDocOrExcelWorksheet

End Sub

Public Sub CheckSpelling() Implements _IOfficeWrapper.CheckSpelling

‘CheckSpelling method delegates call to Document or WorksheetwordDocOrExcelWorksheet.CheckSpelling()

End Sub

Public Sub SaveAs(ByVal fileName As String) _Implements IOfficeWrapper.SaveAs

‘SaveAs method delegates call to Document or WorksheetwordDocOrExcelWorksheet.SaveAs(fileName)

End SubEnd Class

139

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:08 AM Page 139

Page 175: Professional Refactoring in Visual Basic

Basically, the wrapper class delegates calls only to the original Document or Worksheet object. TheDocument or Worksheet object is passed to the wrapper at the moment of wrapper creation. Of course,another very important detail here is that in the wrapper class you have deactivated Option Strict.This way you can treat Document and Worksheet in a polymorphic manner. However, in the wrapperinterface, Option Strict is set to On. So now take a look at how you can make use of the wrapper. Thefollowing code refactors the original CheckSpellingAndSave method so the code is not duplicated andstrict typing is maintained.

‘Activate Option Strict in the wrapper client fileOption Strict On ‘...Public Sub CheckSpellingAndSave(ByVal officeObject As Object)

‘Create wrapper around officeObjectDim officeWrapper as IOfficeWrapper = _ New OfficeWrapper(officeObject) ‘Use wrapper to perform officeObject servicesofficeWrapper.CheckSpelling() officeWrapper.SaveAs(FileName)

End Sub

The only code you had to add to the method was the wrapper creation code. You also had to redirectcalls from the officeObject parameter to officeWrapper.

In short, the dynamically typed wrapper class implements a statically typed wrapper interface anddelegates all calls to an instance of a polymorphic Document or Worksheet object. The Document orWorksheet object is accepted as a parameter by the wrapper constructor method at the moment ofwrapper creation. Figure 5-3 shows a statically typed wrapper interface implemented by a dynami-cally typed wrapper class.

Figure 5-3

Presuming that this is not the only place you’ll use the Office automation objects, the cost of creating andusing the wrapper in order to activate strict typing is quite reasonable. Now you can place Option StrictOn on any file except the file containing the OfficeWrapper source. You have managed to isolate dynamiccode from the rest of the application, minimizing its impact while preserving the benefits of dynamic codeand avoiding code duplication.

<<delegates>>

1 1

<<Option Strict On>>IOfficeWrapper

+CheckSpelling()+SaveAs()

<<Option Strict Off>>OfficeWrapper

+New(in documentOrWorksheet : Object)

Document or Worksheet?

+CheckSpelling()+SaveAs()

140

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:08 AM Page 140

Page 176: Professional Refactoring in Visual Basic

Activating Explicit and Strict Compiler Options

So far, you have seen in great detail the effect Option Strict and Option Explicit have on code.However, I didn’t dedicate too much time to the ways you can control these options. There are few alter-natives available to you in this regard, and I will investigate them in section to follow.

Just imagine you are starting to work on a new project. You have no need to permit undesirable style inyour code if there is a way to prohibit it right from the beginning. However, a number of alternatives areavailable to you for setting the behavior of VB the compiler in regards to Option Explicit and OptionStrict, to help you regulate the compiler’s permissive behavior.

Further, there is more than one place in which you can set compiler options. However, the effect will not always be the same. Therefore, now is a good time to clarify how those settings work and which take precedence.

Setting Options in the Project PropertiesA Visual Basic project has a set of properties that you can configure in the Project Properties window inVisual Studio. You can open this window by right-clicking the project item in the Solution Explorer windowand selecting the Properties menu item. Alternatively, you can select Visual Studio ➪ Project ➪ Properties.

Once the Project Properties window is open, you need to select the Compile tab from the list of the tabson the left-hand side. The window shown in Figure 5-4 will appear. You can observe how the ProjectProperties window enables the configuration of project-wide compiler option settings.

Figure 5-4

141

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:08 AM Page 141

Page 177: Professional Refactoring in Visual Basic

In the “All configurations” section on this page, you will set Option Explicit to On and Option Strictto On. Once you save the new settings, you can inspect the content of the ProjectName.vbproj file in thetop project folder. You need to open this file with some XML or text editor like Notepad — but not withVisual Studio, because this will only load the whole project into the Visual Studio environment and whatyou want is to inspect the actual content of the file. If you take the look at the file’s Last Modification date,you can see that it was modified after you changed the project properties. The file is written in XML format,and it is fairly easy to see how the options in the window relate to the tags in the file. However, after a care-ful search, the only setting related to compiler options is Option Strict written in this form:

<OptionStrict>On</OptionStrict>

Changing the Default Behavior of the Visual Basic CompilerAt this point you might wonder what happened to Option Explicit. This option was also turned on,wasn’t it? Well, the answer has to do with the default behavior of the VB compiler. In case you do not specifyeither of these two options, the VB compiler treats Option Explicit as On and Option Strict as Off bydefault. Therefore, you only need to specify Option Explicit to the compiler if you want to turn it off andOption Strict if you want to turn it on.

Fortunately, there is a way to change the default option values that are loaded when the project is created. That way you do not have to open and set Option Strict and Explicit On each time you create a new project; these options will be turned on by default. To do this, you need to go into VisualStudio and select the Tools ➪ Options. This will open a window for setting different options in theVisual Studio environment. In the tree on the left side of the window you need to locate Projects andSolutions ➪ VB Defaults. Now you can set the project default for Option Explicit and OptionStrict to On. Figure 5-5 shows the Options window in Visual Studio that permits the configuration of default compiler option settings.

This way, each time you create a new project, that project comes up with Option Strict and OptionExplicit set to On by default.

However, the story does not end there. You still have a couple of other ways to control Option Strictand Option Explicit.

Figure 5-5 142

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:08 AM Page 142

Page 178: Professional Refactoring in Visual Basic

Setting Options in Source FilesThere is another place beside the project file to specify Option Explicit and Option Strict. As yousaw earlier in this chapter, you can write these settings in code as a first statement in each source codefile. The files in question have a vb extension. In this case, settings will affect only the code contained inthe same file as the statement. In other words, the scope of the Option statement is limited to the sourcecode file in which the statement is placed.

Visual Studio automatically adds a new file each time you add a class using the Add ➪ New Item optionin the Project menu. This is fine, because having one file per class is the most common way of organizingthe code.

The exception to this model of organizing code comes when a Visual Studio forms designer splits theclass into two by means of partial classes. This way, code generated by the tool is separated from codewritten by the programmer, and this can have a positive effect on code readability.

The fact that you have to repeatedly place statements might make you think that setting options in a sourcefile is impractical. You can have a large number of source code files in the project, so a lot of writing couldbe circumvented by relying on project settings.

However, relying on a project properties file is not always the best option. When you’re working in teamenvironment you might not have the necessary permissions to change the project properties. In anothersituation you might decide to reuse some of your classes at the source level by adding them as sourcefiles to an existing project. In that particular project, options might not enforce strict syntax. So the onlyoption left to you would be to add the Option Explicit On and Option Strict On statements ineach of the source files.

Using Item Templates to Set OptionsIn order to make the task of setting these options less tedious, there is a little trick you can use. When youadd an item to the project using Visual Studio, it comes with a few lines of code that are pre-generated byVisual Studio. This code is based on the item templates that come with the Visual Studio installation. Tohave the Option Explicit On and Option Strict On statements added to a new source file automati-cally, you can modify item templates in Visual Studio so they include these statements. (Another option permits the addition of new templates to the tool, but for the purpose of adding these statements you canmodify existing templates.) You’ll see this is not very complicated.

There’s no need to go into the details about the inner working of Visual Studio templates. Suffice it to saythat templates used to generate VB source code have at least two files, one with a vstemplate extensionand one with a vb extension. All you need to do in order to include the Option Explicit On and OptionStrict On statements in the desired template is to edit the file with the vb extension and place these state-ments on top of the file.

1. First, you need to locate the item templates. They are placed in your Visual Studio installationfolder along this path:

\Microsoft Visual Studio 8\Common7\IDE\ItemTemplatesCache\VisualBasic\1033

where Microsoft Visual Studio 8 is the Visual Studio 2005 installation folder.

143

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:08 AM Page 143

Page 179: Professional Refactoring in Visual Basic

2. Once you open this location in Windows Explorer, you will see a list of directories. Each file represents a single VB item template. Browse the Class.zip folder. It contains two files:

❑ Class.vb

❑ Class.vstemplate

3. You want to edit the Class.vb file. Open it with Notepad, Visual Studio, or some other editorand add Option Explicit On and Option Strict On to the beginning of the file. Table 5-4shows the Class.vb file with and without the option settings.

Table 5-4: Class Item Template Content Before and After the Options Section is Added

4. Then you save the file.

Now each time you add a new class to the project that class will come with Option statements already inthe file. You can repeat this procedure for each .vb file you find inside any of the item template folders.

Class.vb without Option Settings Class.vb with Option Settings

Public Class $safeitemname$

End Class

Option Explicit OnOption Strict On

Public Class $safeitemname$

End Class

A few final words may be in order to address some doubts that might have beenaroused during the procedure I just described. You have probably noticed that thefiles I talked about are placed in the ItemTemplatesCache folder. And if you take alook in Windows Explorer, you will also see the ItemTemplates folder right next tothe ItemTemplatesCache folder. Does this mean that we have performed our modi-fications on some type of cache? If yes, is this cache temporary?

If you open for browsing the following folder:

ProgramFiles\Microsoft Visual Studio 8\Common7\IDE\ItemTemplates\VisualBasic\1033

you will see a number of zipped files. The name of each file coincides with a foldername in the ItemTemplatesCache directory. As a matter of fact, the Cache folder wascreated by extraction of the ItemTemplates folder during Visual Studio setup. VisualStudio lets you repeat some of the last steps performed during the Visual Studioinstallation process by invoking the Setup command on devenv.exe. In that case,ItemTemplatesCache will be replaced with the content from the ItemTemplatesfolder. If you for some reason need to repeat the Visual Studio setup process, youmight lose the changes we have just performed. In order to avoid this, you can unzipthe template file in the ItemTemplates folder, apply modifications on the vb files,and create the zip archive again. This way, even if you have to repeat some of the installation steps, you will not lose the changes that included the addition of the Option Explicit On and Option Strict On statements to the vb files.

Finally, a word of warning: if you do decide to modify the ItemTemplates folder,back it up before modifying the files, just in case something goes wrong.

144

Part II: Preliminary VB Refactorings

79796c05.qxd:WroxPro 2/23/08 8:08 AM Page 144

Page 180: Professional Refactoring in Visual Basic

SummaryYou have just seen a number of implications that activating Option Strict and Option Explicit canhave on your code. When these options are activated, you must declare all variables and their types andperform all narrowing conversions explicitly. You have seen different ways to set these options in codeon the project level, and even how to change default settings in VB IDE.

You have seen a number of benefits that activating these two options can bring. However, in many situa-tions you are faced with existing code that has been written without these two options being active. It ispossible to convert this code into statically and strongly typed code. This requires a methodical and sys-tematic approach and can be cumbersome work, as it requires you to infer variable type based on con-text. Fortunately, you can count on some help from the compiler. You have even seen the first practicaluse of the Refactor! add-in and how it can help you in the undertaking by automating the task of split-ting the local variable.

Finally, you have seen some situations in which dynamic typing can actually be beneficial. I have alsodemonstrated how to make dynamically and statically typed code cohabitate, so you can benefit fromthe best of the both worlds.

In the next chapter you’ll continue to investigate refactorings specific to Visual Basic. I’ll discuss more sit-uations in which Visual Basic is giving you a lot of choice in the way you can program — but remember,not all choices are equally good. Often certain language elements are remnants from the past, and youshould avoid them. This is the topic of discussion in the next chapter, which deals with error handling.

145

Chapter 5: Chameleon Language

79796c05.qxd:WroxPro 2/23/08 8:08 AM Page 145

Page 181: Professional Refactoring in Visual Basic

79796c05.qxd:WroxPro 2/23/08 8:08 AM Page 146

Page 182: Professional Refactoring in Visual Basic

Error Handling: From Legacy to Structured

in a Few Easy Steps

Error handling in software is similar to an emergency exit in real life. Nobody usually gives it toomuch thought, since you almost never use it. However, once you need it, you definitely want it toperform impeccably. Emergencies and unexpected situations happen in software with more fre-quency than in real life, so the effect a badly handled exception can have on user confidence can be quite devastating. Nothing is more annoying for users than an application simply disappearingfrom the screen for apparently no reason at all, or receiving a number of cryptic messages insteadof having tasks performed.

Error handling is an essential element of well designed and robust code. While it deals with atypicaland unexpected situations, it is a crucial ingredient for providing a robust and reliable application.Successfully implemented error handling permits execution to resume without the application halt-ing or crashing. It will give the programmer a chance to inform a user about the current state of theapplication in an understandable way, instead of showing some cryptic system messages. In somecases the user can be consulted on the way the application should continue. Coupled with logging,error handling can also be used to save the information on the state of the system when the erroroccurred, providing the programmer with valuable insight into the cause of the error and the loca-tion in the code where it occurred. This greatly simplifies debugging and maintenance.

In this chapter you are going to examine two different styles of error handling: legacy and struc-tured. You will see why I prefer the structured style and how you can refactor legacy style intostructured. Here is what I plan to talk about in this chapter:

❑ I’ll start by recapping how legacy error handling is implemented in VB. This style of errorhandling uses error codes and the On Error statement.

❑ Then I’ll take you through a look at structured error handling. Since it uses exceptiontypes instead of error codes, it is also referred to as exception handling. Structured errorhandling is realized through the use of structured Try-Catch-Finally blocks.

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 147

Page 183: Professional Refactoring in Visual Basic

❑ After I compare the two styles, you’ll see how structured error handling is superior and whyyou should always strive to stick with this style of error handling.

❑ Since you are bound to come across code that is still making use of legacy error handling, I willdemonstrate how you can convert legacy to structured error handling. You do this through tworefactorings. The first one involves replacing the On Error statement with Try-Catch-Finallyblocks and is accordingly called Replace On Error Construct with Try-Catch-Finally. Thesecond refactoring you’ll deal with in this chapter replaces error code with exception type. It’scalled Replace Error Codes with Exception Type.

I want to start by taking a look at the different ways error handling in VB is implemented and how thesestyles compare.

Legacy Error Handling Versus StructuredError Handling

In VB .NET you have two distinct styles of exception handling available to you. The first style has its origins in pre-.NET versions of VB, so I will refer to it as legacy or unstructured error handling. It uses thefamiliar On Error construct in combination with the Goto or Resume Next keyword.

With VB .NET you can also use the Try-Catch-Finally keywords. This style of error handling is calledstructured because error-handling code is organized in the form of structured blocks instead of by meansof a Goto statement that controls the flow of execution.

While both styles can be used and even mixed in VB .NET, only a single style can be used in a single pro-cedure. You can still use both styles in a single class, as long as they are applied to different procedures.Later on in this chapter, when you start dealing with the conversion of legacy error handling, you’ll seehow this coexistence can be very useful for performing conversion gradually.

First I want to take a look now at how each of these error-handling styles works.

Legacy (Unstructured) Error HandlingThis style of error handling predates VB .NET. Error information is available through the global Errobject. The most important properties of the Err object are Number, related with the cause of error, andthe description. You frequently encounter two flavors of this style of error handling.

The On Error Construct in Combination with the Goto Statement, Label, and Exit Sub Flavor

Placing an On Error Goto label line inside the method results in the execution flow continuing on thefirst line after the label in the case of an error. The label is generally placed just after the Exit Sub state-ment, so in normal flow, code below the label is never executed. It will be best if I illustrate this withsome code. Figure 6-1 shows unstructured error handling using the On Error Goto label.

148

Part II: Preliminary VB Refactorings

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 148

Page 184: Professional Refactoring in Visual Basic

Figure 6-1

This is fairly simple code; all it does is write a few characters into a file. Two possible problems can occurwith this code:

❑ The file can be missing

❑ The file can be marked as read-only

In case of error, execution continues on the first line after the ErrorHandler label. This happens for anytype of error, so Err.Number has to be used to determine the cause of the error. This way, error handlingcan be adjusted depending on the type of error. In this case, depending on the type of error, a differentmessage is displayed.

The On Error Resume Next Statement FlavorPlacing an On Error Resume Next statement at the top of method results in execution resuming normalexecution flow in the case of an error, just as if the error never happened. Fortunately, error information iskept in the Err object, so Err.Number and other properties can be inspected for the presence of an error.Take a look at the previous example written in the Resume Next flavor (Figure 6-2).

In this case you can see If Err.Number =... blocks scattered throughout the code. As a matter of fact,this block should follow any line you are suspicious of. If you think an error can occur at some line, youshould check if Err.Number is different from zero on the following line.

The Resume Next flavor has error-handling code scattered throughout the procedure, while with theGoto label flavor, error-handling code is generally placed at the bottom of the procedure. This is the maindifference between these two flavors. It is time now to take a look at structured error handling.

Imports System.IO

Module OnErrorDemo

Sub Main() On Error GoTo ErrorHandler Dim writeTo As Stream = _ New FileStream("C:\SomeFile.txt", _ FileMode.Open) Dim fileWriter As TextWriter = _ New StreamWriter(writeTo) fileWriter.WriteLine("Hello!") fileWriter.Close() Exit SubErrorHandler: Select Case Err.Number Case 5 MsgBox("File access denied. " & _ "Check file and " & _ "directory write permissions.") Case 53 MsgBox("File SomeFile.txt not found.") End Select End SubEnd Module

Error handlingflow

File not found orread-only, jump to

label

Normal flow

Terminates normal flowCheck error type

149

Chapter 6: Error Handling

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 149

Page 185: Professional Refactoring in Visual Basic

Figure 6-2

Structured Error HandlingWith structured error handling you use the Try-Catch-Finally keywords to organize code into the struc-tured blocks. Inside the Try block you place statements that can potentially produce error. In the Catchblock you place the code that deals with error. In the Finally block you place the code that you want toexecute always, whether an error occurs or not. This is generally cleanup code, used for closing and releas-ing different resources.

The Exception Is Represented by a Single ObjectIn structured error handling you do not use a global Err object as a source of error information. Instead,each error is represented by its own object. This object is referred to as an exception. This is why, as I statedearlier, in VB .NET you more commonly use the term exception handling to refer to error handling. Since eachof these objects is an instance of a specific exception class, you can use the type of the exception to distin-guish different causes of error. This, combined with capacity to catch specific exceptions in a Catch block,enables you to react to a specific exception without conditional code that inspects an Err.Number, as inunstructured error handling. In legacy error handling you had to check the type of exception with code:Select Case Err.Number... and If Err.Number = SomeNumber Then .... This is no longer neces-sary in structured error handling because a Catch block can be related to a certain exception type by meansof an As keyword. This way you can filter errors in Catch blocks.

If you do not specify the error class or if you catch System.Exception, any exception that occurs iscaught in that block. In this case, it is important to watch the order of Catch blocks. More specific excep-tions should be caught first. Accordingly, System.Exception as the most generic should be placed last. If you place System.Exception as the first Catch block by mistake, other Catch blocks will never be

Imports System.IO

Module OnErrorDemo

Sub Main() On Error Resume Next Dim writeTo As Stream = _ New FileStream("C:\SomeFile.txt", _ FileMode.Open) If Err.Number = 5 Then MsgBox("File access denied. " & _ "Check file and " & _ "directory write permissions.") Exit Sub ElseIf Err.Number = 53 Then MsgBox("File SomeFile.txt not found.") Exit Sub EndIf Dim fileWriter As TextWriter = _ New StreamWriter(writeTo) fileWriter.WriteLine("Hello!") fileWriter.Close() End SubEnd Module

Terminates Error Handling Flow

Error handlingflow

Normal flow,jump to bottomTerminates Error Handling Flow

150

Part II: Preliminary VB Refactorings

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 150

Page 186: Professional Refactoring in Visual Basic

executed. System.Exception will be executed under any exceptional condition, and because only a singleCatch block is ever executed in a Try-Catch-Finally construct, other blocks will become redundant.

I just mentioned that System.Exception is the most generic exception. In the next section you’ll seehow exceptions can be more or less generic and the way the exception hierarchies can be formed.

Exception HierarchiesException classes are generally organized in inheritance hierarchies. At the root of the hierarchy isalways the System.Exception class. This organization enables you to catch exceptions at different levels of abstraction. Going back to the code sample used in this chapter, in some cases you might not care whether the exception was provoked because the file was read-only or the file was missing. It is enough to know that some type of file-access error occurred. In this case, both System.IO.FileNotFoundException and System.UnauthorizedAccessException have a common super-type in their hierarchies. It’s System.SystemException. You can deal with both errors in a singleCatch block if you catch System.SystemException. Take a look at the exception classes inheritancehierarchy in Figure 6-3.

Figure 6-3

You can clearly see in the figure how the first common base class for both System.IO.FileNotFoundException and System.UnauthorizedAccessException is System.SystemException. If you catch this exception in the form of the Catch exception AsSystem.SystemException, the Catch block will be executed no matter which of the two exceptions occurred.

System.Exception

System.SystemException System.ApplicationException

System.UnauthorizedAccessExceptionSystem.IO.IOException

System.IO.FileNotFoundException

151

Chapter 6: Error Handling

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 151

Page 187: Professional Refactoring in Visual Basic

You can implement your own exceptions by writing classes that inherit from System.ApplicationException. On the other hand, the .NET Framework exception classes generallyinherit System.SystemException, and these errors are thrown by the Common Language Runtime(CLR). You can just as well inherit System.Exception, but the convention is that you should inheritApplicationException when implementing your own custom exceptions.

Now take another look at the code sample and see how it can be implemented by means of structurederror handling. This is illustrated in Listing 6-1.

Listing 6-1: A Sample of Structured Error Handling

Imports System.IO

Public Class OnErrorDemo

Shared Sub Main()

Dim fileWriter As TextWriter = NothingDim writeTo As Stream = Nothing‘Activate error handlingTry

writeTo = _New FileStream(“C:\SomeFile.txt”, _FileMode.Open)fileWriter = New StreamWriter(writeTo)fileWriter.WriteLine(“Hello!”)

‘Catch FileNotFoundExceptionCatch fileNotFound As FileNotFoundException

MsgBox(“File SomeFile.txt not found.”)‘Catch UnauthorizedAccessExceptionCatch unauthorizedAccess As UnauthorizedAccessException

MsgBox(“File access denied. “ & _“Check file and “ & _“directory write permissions.”)

‘Catch any other IO exception, beside FileNotFoundExceptionCatch IOException As System.IO.IOException

MsgBox(“Some file related exception occurred”)‘Catch any other exception, if neither FileNotFoundException ‘nor UnauthorizedAccessException nor any IOException occurredCatch exception As System.Exception

MsgBox(“Unexpected error occurred”)‘Execute always, no matter if exception occurred or notFinally

If fileWriter IsNot Nothing _Then fileWriter.Close()If writeTo IsNot Nothing Then _writeTo.Close()

End TryEnd Sub

End Class

152

Part II: Preliminary VB Refactorings

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 152

Page 188: Professional Refactoring in Visual Basic

Right away this code is easier to follow and understand. Further, Try-Catch-Finally blocks are scoped.This means that variables declared inside these blocks are not visible to code on the outside. That is whyI had to declare fileWriter and writeTo variables outside of the Try block, because I accessed thosevariables in the Finally block. This helps you structure and modularize your code further.

And the story about structured error handling does not end here. For example, error-handling blockscan be nested. However, for the purpose of this chapter, you have enough detail about structured errorhandling. It’s time to compare it to legacy error handling.

Now you are basically familiar with both unstructured and structured error handling. So how do theymeasure up? Why was the new style of error handling introduced and is it really superior? Why shouldyou prefer structured error handling? Should you replace unstructured error handling with structurederror handling? The next section answers these questions.

The Benefits of Structured Error Handling

Structured Versus Unstructured CodeIn VB .NET, specific control structure for error handling was introduced. Instead of using an awkwardconstruct like a Goto keyword in combination with a label, you can use Try–Catch–Finally blocksinstead. The Goto keyword in combination with a label is an example of legacy, unstructured program-ming and can result in complicated spaghetti code with awkward jumps in execution flow that are diffi-cult to understand and follow. Similar problems are inherent to the Resume Next construct, in whicherror-handling code is scattered and can easily be left unimplemented. In contrast to this, if a Try blockis placed, at least one Catch block has to be implemented in order for code to compile.

Smell: Legacy (Unstructured) Error HandlingDetecting the SmellUse a simple text search to locate On Error keywords in your code.

Related RefactoringUse Replace On Error with Try-Catch-Finally refactoring to eliminate this smell.

RationaleLegacy (unstructured) error handling relies on unstructured Goto statements and labelsin code for execution flow control, resulting in awkward code that is difficult to under-stand and maintain. Additional effort has to be introduced in order to filter errors, addingunnecessary conditional statements to error-handling code. Unstructured error handlingdoes not permit easy implementation of cleanup code, achieved by means of Finallyblocks in structured error handling.

Improved error handling was for quite some time on the list of desired features of VB. Moving VB forward on the path of object orientation and making it fully .NET-interoperable required a new solution for error handling. New keywords were intro-duced, error handling was implemented in a structured manner, and errors wererepresented as class instances. This has significantly improved the way error handlingis implemented. The next sections take a look at the major advances achieved in VB .NET.

153

Chapter 6: Error Handling

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 153

Page 189: Professional Refactoring in Visual Basic

Exceptions as Types, Not Numbers

In legacy error handling, error information was provided at runtime through a global Err object. In order todifferentiate between different types of errors, you had to look up an Err.Number property. The only way to throw and signal a custom error was to use a specific error number. Even when you are using enumera-tors to make these numbers more meaningful, code readability still suffers.

With structured error handling, exceptions are fully fledged programmable objects. The name of the classof the exception is much more descriptive than some error code. Furthermore, IntelliSense will naturallylend help when you are writing Catch exception As ... code.

Related to the programmability of exception classes is the manner in which custom exceptions can beimplemented. Custom exceptions are implemented by the extension of System.Exception or someother derived class within the Exception inheritance hierarchy. Some of the Exception members, likeMessage property, are then generally overridden in order to provide custom error information. Now,going even further in developing robust design of your system, if some elaborate error-handling mecha-nism is applied, custom exceptions can be enriched with some new members. For example, you mightadd the Time property to all exceptions in your application that could tell you the exact time the excep-tion was produced. In order to do this, it is enough to add this property to the custom Exception classat the top of your Exception inheritance hierarchy.

This results in another benefit. By being able to catch any exception along the inheritance hierarchy, you can avoid a lot of code duplication. If you can react in the same way to a group of errors, and theseerrors are all children of the same base class, it is enough to catch the base class exception instance onetime and to write the error-handling code just once.

Error FilteringRelated to the previous issue and the structured organization of error-handling code is the filteringcapacity of multiple Catch blocks. If we specify an exception type in the Catch block in the form of

Smell: Error CodeDetecting the SmellUse a simple text search to locate Err.Raise() and Catch When Err.Numberstatements in your code.

Related RefactoringUse Replace Error Code with Error Type refactoring to eliminate this smell.

RationaleThe legacy style of VB error handling uses error codes to signal the type of error. Thecodes are simple numbers and much more difficult to interpret than exception types.This has a negative impact on code comprehension even when error constants or enu-merations are used to make error codes more meaningful. Using the global Err object toraise an error limits the capacity to customize error objects, because the Err object can-not be extended. This style can also have a negative impact on the .NET interoperabilityof reusable components and libraries. Error code information cannot cross assemblyboundaries unless a predefined system error is raised, resulting in any but a systemerror appearing as a generic System.Exception in client code.

154

Part II: Preliminary VB Refactorings

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 154

Page 190: Professional Refactoring in Visual Basic

Catch exception As ExceptionType, only if the specified exception type coincides with the cur-rent exception will the execution flow continue in that specific Catch block. Because of this, controlstructures like Select Case Err.Number ... and If Err.Number Then ... are no longer neces-sary. This adds to code simplicity and readability.

The Finally BlockIn unstructured error handling there is no simple way to have a piece of code execute both when anerror occurs and during normal execution flow. Such code, generally used for object cleanup and therelease of resources, can be crucial for providing robust and stable code that does not leak memory.With structured error handling, you can easily set up this type of code using the Finally block.

In legacy error handling, you cannot do this without using multiple Goto statements and labels, adding to code complexity and awkwardness. So, as a more simple approach, programmers working with legacyerror handling often ended up placing cleanup code both in normal execution and in error-handling code.Duplicated code is one of the most undesirable features in code. With the Finally block in structurederror handling, it can easily be avoided.

.NET InteroperabilityImagine you are developing a certain reusable piece of code, like a component or a class library. In thiscase exceptions should be a part of your contract with the library or component users. You should pro-vide them with comprehensive error information, so that they can react appropriately to any exceptionyour code can raise.

In legacy error handling you use the Raise method of the Err object in order to throw an error. This methodaccepts the Number parameter as the only non-optional parameter. Now, as long as you know the systemerror numbers, you can throw system exceptions by using Err.Raise(SystemErrorNumber). For exam-ple, Err.Raise(53) results in a FileNotFoundException exception thrown to the client code. VB runtimeautomatically translates Err.Raise(53) to the more .NET-friendly Throw New FileNotFoundException.This way, clients can use structured error handling and specify the type of error they wish to handle in aCatch block by using a Catch exception As FileNotFoundException statement.

However, once you try to raise any custom error using your own nonsystem error code, for example inthe form of Err.Raise(vbObjectError +10), a basic System.Exception is thrown and clients haveto handle System.Exception for any custom error code. There is no way for VB runtime to translatevbObjectError +10 to any meaningful exception type, so the generic System.Exception is thrown.Not really helpful to the client programmers.

On the other hand, if you stick to structured error handling and program your custom exceptions byinheriting System.Exception, the client receives much more complete information and will be able to filter different custom exceptions in multiple Catch blocks. That’s definitely a much more friendlyoption for client programmers.

The benefits of structured error handling do not end here, but all the same I think you have seen enougharguments in its favor. Are there any cases where legacy error handling is preferable? I don’t know of any.However, the chances are you’ll come across it. One sure source of this style of error handling is pre-.NETVB code. Migration tools leave error handling as is, because there is no way to automatically transformlegacy to structured error handling in a meaningful way. That’s all the more reason why you should

155

Chapter 6: Error Handling

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 155

Page 191: Professional Refactoring in Visual Basic

explore the ways you can perform these transformations yourself. Because such transformations are con-cerned with improving the code without changing its behavior, they can rightfully be called refactorings.I’ll start off by examining how the On Error construct can be replaced with Try–Catch–Finally.

Replacing the On Error Construct with Tr y-Catch-F inally

Refactoring: Replace the On Error Construct with Try-Catch-FinallyMotivationStructured error handling is in many ways superior to legacy-style error handling:

❑ It uses structured constructs for execution flow control, as opposed to theunstructured Goto or Resume Next.

❑ It has dedicated a error-filtering facility in the form of the Catch excep-tion As ExceptionType construct.

❑ It uses types, not numbers, to represent exceptions.

❑ It improves the .NET interoperability of Visual Basic code.

Related SmellsUse this refactoring to eliminate the Legacy (Unstructured) Error Handling smell.

MechanicsStart by identifying error-protected and error-handling code. After that, perform thefollowing steps:

1. Replace On Error ... with a Try statement. Mark the end of the pro-tected code with a Catch statement. Move the error-handling code insidethe Catch block.

2. If the error-handling code is conditioned by the type of error with the com-bination of Select Case or If statements and Err.Number, replace con-ditional code with Catch When Err.Number = ... blocks.

3. Because Try–Catch–Finally blocks are scoped, variables have to bedeclared outside the Try block if you need to access them as a part of yourerror-handling routine in Catch or Finally blocks. You might need tomove the variable declaration outside the Try block.

4. If duplicated statements are inside the error-protected and error-handlingcode, add a Finally block and move these duplicated statements to theFinally block. This way you eliminate some duplication in your code.

BeforeImports System.IO

Public Class OnErrorDemo

156

Part II: Preliminary VB Refactorings

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 156

Page 192: Professional Refactoring in Visual Basic

Shared Sub Main()On Error GoTo ErrorHandlerDim writeTo As Stream = _New FileStream(“C:\SomeFile.txt”, _FileMode.Open)Dim fileWriter As TextWriter = _New StreamWriter(writeTo)fileWriter.WriteLine(“Hello!”)fileWriter.Close()Exit Sub

ErrorHandler:Select Case Err.Number

Case 5MsgBox(“File access denied. “ & _“Check file and “ & _“directory write permissions.”)

Case 53MsgBox(“File SomeFile.txt not found.”)

End SelectEnd Sub

End Class

AfterImports System.IO

Public Class OnErrorDemo

Shared Sub Main()Dim fileWriter As TextWriter = NothingDim writeTo As Stream = NothingTry

writeTo = _New FileStream(“C:\SomeFile.txt”, _FileMode.Open)fileWriter = _New StreamWriter(writeTo)fileWriter.WriteLine(“Hello!”)fileWriter = NothingfileWriter.Close()

Catch When Err.Number = 5MsgBox(“File access denied. “ & _“Check file and “ & _“directory write permissions.”)

Catch When Err.Number = 53MsgBox(“File SomeFile.txt not found.”)

FinallyIf fileWriter IsNot Nothing Then _fileWriter.Close()If writeTo IsNot Nothing Then _writeTo.Close()

End TryEnd Sub

End Class

157

Chapter 6: Error Handling

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 157

Page 193: Professional Refactoring in Visual Basic

There is no doubt that structured error handling is making our code more robust, more maintainable,and easier to comprehend. Therefore, it is worthwhile to invest time to replace legacy with structurederror handling. At this point, the only question is how difficult and costly this can be.

I want to demonstrate how this can be performed in a few relatively simple steps and can actually be simpler than it might look at first glance. I’ll illustrate these steps on both flavors of legacy error handling.Before I can do this, however, I want you to examine one less common Visual Basic keyword that will beof huge help in this task.

Understanding the When KeywordYou have already seen how it is possible to write different Catch blocks for different types of exceptions.The only block that is executed is the one in which the declared exception type coincides with the type of exception that has been produced. You have used Catch exception As SomeException syntax toachieve this.

It is also possible to condition the execution of the Catch block. You do this by combining the Catchblock with the When keyword. In this case the Catch block executes only if the expression following theWhen keyword evaluates to true. For example, the following Catch block will be executed if the vari-able NumberOfTries represents a value greater than three:

Try‘Some error-protected codeCatch When NumberOfTries > 3‘Some error handling code

Because the Err object is always present, even if you do not use the On Error construct, you can combinethe When keyword with the Err.Number property in order to condition Catch blocks. So you can write thecode similar to this:

Try‘Some error-protected codeCatch When Err.Number = 5‘Some error handling codeCatch When Err.Number = 53‘Some error handling code

At this point you may already be guessing what use you are going to give to the When keyword in thischapter, so now I want to turn to the steps you need to perform in order to convert legacy error-handlingcode to structured error-handling code.

Refactoring Steps for Replacing On Error with Try-Catch-Finally

More complex refactorings are often composed from simple ones. In that case, it is important to bear in mind that those smaller refactorings also have to behave as complete and atomic refactorings. Thismeans that each step of the way during a complex refactoring, you want to still end up with code youare able to compile and that performs in the same way the original code did. Remember the refactoringdefinition from Chapter 1? No change in behavior in the code will take place. This focus on atomic and

158

Part II: Preliminary VB Refactorings

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 158

Page 194: Professional Refactoring in Visual Basic

complete smaller refactorings also helps you in being able to execute tests and ensure progress eachstep of the way of complex refactoring.

What then are the steps you need to perform when transforming legacy error handling to structurederror handling? You start out by identifying the error-protected code that executed under normal circumstances and the code that deals with error. After that you perform the following steps:

1. Replace the On Error structure with Try–Catch so the code that was protected from the error isnow placed inside a Try block and the code that dealt with error is placed inside a Catch block.

2. Replace error-filtering code that uses the Select Case or If construct with the Catch Whenconstruct.

3. This step deals with two situations related to cleanup code:

❑ Cleanup code is duplicated and present both inside error-protected code executed dur-ing normal execution and inside error-handling code. Place code that should be executedboth during normal execution flow and in case of error inside the Finally block. In legacyerror handling, this placing of the cleanup code both inside the error-protected code andinside the error-handling code was a typical way of implementing error handling. If code is present in both places, you can extract this code and place it inside the Finally block.

❑ Cleanup code is not present or is present only in normal, error-protected code.Optionally, you can add or move the cleanup code to the Finally block. This step isoptional and in a way controversial because it does not follow the strict definition ofrefactoring. If you are adding new code, then the way the code behaves will change.Now, if you know very well what you are doing, you might prevent some memoryleaks and similar problems by placing cleanup code inside a Finally block. If youhave any doubts, leave it be for now and do it as part of some maintenance work, butnot as part of the refactoring process. Even if you leave this step out, your code willstill perform as it used to, and if you didn’t notice any problems so far the issue doesnot need any urgent attention.

Now I want to turn in detail to how these steps should be performed when applied to two of the most common flavors of legacy error handling. As sample code, I will use the examples already seen in Figures 6-1 and 6-2. If you follow the listings to come, you will be able to observe the gradual evolu-tion of the code.

Replacing the On Error Goto Label with the Try-Catch-Finally Construct

The first thing you need to do is to identify the error-protected and error-handling code. Go back toFigure 6-1 for a moment. You will see the arrow marking the normal flow. You can see that this code isplaced between the On Error GoTo ErrorHandler and Exit Sub lines. Now identify the error-handlingcode. This code is marked with the “Error handling flow” arrow: it starts below the ErrorHandler: labeland ends with End Sub. It is time to refactor this code.

Replacing On Error GoTo ErrorHandler with the Try KeywordReplace the Exit Sub and Error Handler: lines with a single Catch statement. Finally, place the EndTry statement before the End Sub statement. See Listing 6-2 for original code with comments indicatingplace for Try and End Try statements.

159

Chapter 6: Error Handling

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 159

Page 195: Professional Refactoring in Visual Basic

Listing 6-2: Replace On Error GoTo ErrorHandler with Try-Catch

Imports System.IO

Public Class OnErrorDemo

Shared Sub Main()‘Replace with “Try”On Error GoTo ErrorHandler Dim writeTo As Stream = _New FileStream(“C:\SomeFile.txt”, _FileMode.Open)Dim fileWriter As TextWriter = _New StreamWriter(writeTo)fileWriter.WriteLine(“Hello!”)fileWriter.Close()‘Replace following two lines with “Catch”Exit Sub

ErrorHandler: Select Case Err.Number

Case 5MsgBox(“File access denied. “ & _“Check file and “ & _“directory write permissions.”)

Case 53MsgBox(“File SomeFile.txt not found.”)

End Select‘Place “End Try” here

End Sub

End Class

This is only an intermediate step, but if you try you will see that you can still compile the code withoutany problem, and it will behave in the same way it did before. Take a peek at Listing 6-3 to see the codewe ended up with. Next, it’s time to deal with error filtering.

Replacing Select Case Err.Number with Catch When Err.Number = ...You can see how error-handling code behaves differently depending on the type of error. In the code,this translates into the Select Case statement using different values for Err.Number as a condition.Now is the time to make use of the When keyword.

You eliminate the Select Case statement. The code for Case 5 is placed under a Catch When Err.Number= 5 block and the code for Case 53 is placed under a Catch When Err.Number = 53 block.

Listing 6-3: Replace Select Case Err.Number with Catch When Err.Number = ...

Imports System.IO

Public Class OnErrorDemo

Shared Sub Main()

160

Part II: Preliminary VB Refactorings

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 160

Page 196: Professional Refactoring in Visual Basic

Listing 6-3: Replace Select Case Err.Number with Catch When Err.Number = ... (continued)

TryDim writeTo As Stream = _New FileStream(“C:\SomeFile.txt”, _FileMode.Open)Dim fileWriter As TextWriter = _New StreamWriter(writeTo)fileWriter.WriteLine(“Hello!”)fileWriter.Close()

‘DeleteCatch

‘Delete after adding Catch When conditionSelect Case Err.Number

‘Replace with “Catch When Err.Number = 5Case 5

MsgBox(“File access denied. “ & _“Check file and “ & _“directory write permissions.”)

‘Replace with “Catch When Err.Number = 53”>Case 53

MsgBox(“File SomeFile.txt not found.”)‘DeleteEnd Select

End TryEnd Sub

End Class

With this, you have finalized the transformation. Now you can compile and execute the code. It will per-form exactly the same as the legacy code you started off with.

Adding a Finally BlockIf you have code that is duplicated in such a way that it is present both in the main flow and in theerror-handling code, you can extract it and place it under a Finally block. In this example you do nothave duplicated code, but you might want to place cleanup code inside the Finally block. As I havealready mentioned, this step is optional, because it does change the behavior of your code. Extra atten-tion is advised.

In the sample you should close the Stream and Writer both after normal and after error-handling exe-cution. So you can add a Finally block and place the fileWriter.Close() and writeTo.Close()statements inside it. However, since Try–Catch–Finally blocks are scoped, you need to declare thewriteTo and fileWriter variables outside the Try block. Leave the creation code inside the Try block,because it is exactly the moment the error might be produced.

Because of block variable scope, as a precaution, before closing the Stream and Writer you need tomake sure that Stream and Writer objects were actually created. If you do not check this, you might get a NullReferenceException if the error was produced in the moment of object creation.

Listing 6-4 is what the code looks like after the Finally block is added.

161

Chapter 6: Error Handling

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 161

Page 197: Professional Refactoring in Visual Basic

Listing 6-4: Optional Finally Block Added

Imports System.IO

Public Class OnErrorDemo

Shared Sub Main()‘Variable declaration outside Try blockDim fileWriter As TextWriter = Nothing Dim writeTo As Stream = Nothing Try

writeTo = _New FileStream(“C:\SomeFile.txt”, _FileMode.Open)fileWriter = _New StreamWriter(writeTo)fileWriter.WriteLine(“Hello!”)

‘Conditional “Catch” blockCatch When Err.Number = 5

MsgBox(“File access denied. “ & _“Check file and “ & _“directory write permissions.”)

‘Conditional “Catch” blockCatch When Err.Number = 53

MsgBox(“File SomeFile.txt not found.”)‘Finally block, fileWriter and writeTo variables accessed Finally

If fileWriter IsNot Nothing Then _ fileWriter.Close()If writeTo IsNot Nothing Then _writeTo.Close()

End TryEnd Sub

End Class

This wraps up the transformation of the On Error Goto ErrorHandler flavor of legacy error handling.The next section deals with the Next flavor.

Replacing On Error Resume Next with the Try-Catch-Finally Construct

Just as in the previous section, you start out with identifying the error-protected and error-handlingcode. If you go back to Figure 6-2 you can see that error-protected code starts with the On ErrorResume Next statement and is in operation until the end of the procedure, which ends with the EndSub statement. Error-handling code is present in an If block. The following sections take you throughthe refactoring steps.

Replacing On Error Goto Error Handler with Try KeywordAdd Catch and End Try statements before End Sub. Move the If block containing the error-handlingcode to the Catch block. After that, you can remove the redundant Exit Sub statements from the Ifblock. See Listing 6-5.

162

Part II: Preliminary VB Refactorings

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 162

Page 198: Professional Refactoring in Visual Basic

Listing 6-5: Replace On Error Resume Next with a Try Block

Imports System.IO

Public Class OnErrorDemo

Shared Sub Main()‘Replace with “Try”On Error Resume Next Dim writeTo As Stream = _New FileStream(“C:\SomeFile.txt”, _FileMode.Open)‘1. Place following two lines: first “Catch” then “End Try”>‘2. Move “If” block inside “Catch” block and ‘ remove “Exit Sub” statements along the wayIf Err.Number = 5 Then

MsgBox(“File access denied. “ & _ “Check file and “ & _ “directory write permissions.”) Exit Sub

ElseIf Err.Number = 53 Then MsgBox(“File SomeFile.txt not found.”) Exit Sub

End If Dim fileWriter As TextWriter = _New StreamWriter(writeTo)fileWriter.WriteLine(“Hello!”)fileWriter.Close()

End SubEnd Class

Replacing If Err.Number = ... with Catch When Err.Number = ...This step is quite similar to Step 2 in the refactoring of the On Error GoTo ErrorHandler flavor of legacy error handling you did earlier. Again, you use two Catch When Err.Number = ... blocksto replace the If block. The resulting code is the same as that obtained after Step 2 in the previous flavor’s refactoring.

Adding a Finally BlockAt this point, the refactoring of the On Error Resume Next and On Error GoTo label flavors of legacyerror-handling code converge. So the actual step here is equal to the last step used in the conversion of the On Error GoTo label flavor of legacy error handling. The resulting code is the same as that shown in Listing 6-4. This means that both flavors of legacy error handling were transformed into the same finalversion of structured error-handling code.

With this, you have successfully eliminated On Error statements from your code. Now you are usingstructured Try–Catch–Finally blocks. However, one piece is still missing in the complete replacementof legacy error-handling code with structured code. You are still using the global Err object and needsome way to get rid of it.

163

Chapter 6: Error Handling

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 163

Page 199: Professional Refactoring in Visual Basic

Replacing Error Code with Exception Type

Refactoring: Replace Error Code with Exception TypeMotivationUsing classes to represent exceptions instead of representing them through numbers isa far superior way to classify and organize exceptions. The code will be more readableand robust, will contain less duplication, and will be much more .NET-interoperable. Inaddition, exception classes can then be extended and organized in logical hierarchies,and their behavior customized if needed.

Related SmellsUse this refactoring to eliminate the Error Code smell.

MechanicsThis refactoring should follow the Replace On Error Construct with Try-Catch-Finally refactoring.

Dealing with System ExceptionsWhen raising exceptions, replace the code-raising exception by means of Err object, asin an Err.Raise(SomeErrorCode) statement with code throwing an exception by themeans of the Throw keyword, as in the Throw New SomeException statement.

When catching exceptions, replace Catch When Err.Number = SomeErrorCodewith Catch exception as SomeSystemException.

Dealing with Custom ExceptionsWhen raising exceptions, replace the custom error code with a new custom exceptionclass and use the Throw keyword instead of the Err object’s Raise method.

When catching exceptions, replace statements like Catch When Err.Number =SomeErrorCode with code such as Catch exception as SomeCustomException.

Code sample for system error codes:

BeforeImports System.IO

Public Class OnErrorDemo

Shared Sub Main()Dim fileWriter As TextWriter = NothingDim writeTo As Stream = NothingTry

writeTo = _New FileStream(“C:\SomeFile.txt”, _FileMode.Open)fileWriter = _New StreamWriter(writeTo)fileWriter.WriteLine(“Hello!”)

164

Part II: Preliminary VB Refactorings

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 164

Page 200: Professional Refactoring in Visual Basic

Continued

Catch When Err.Number = 5MsgBox(“File access denied. “ & _“Check file and “ & _“directory write permissions.”)

Catch When Err.Number = 53MsgBox(“File SomeFile.txt not found.”)

FinallyIf fileWriter IsNot Nothing Then _fileWriter.Close()If writeTo IsNot Nothing Then _writeTo.Close()

End TryEnd Sub

End Class

AfterImports System.IO

Public Class OnErrorDemo

Shared Sub Main()Dim fileWriter As TextWriter = NothingDim writeTo As Stream = NothingTry

writeTo = _New FileStream(“C:\SomeFile.txt”, _FileMode.Open)fileWriter = _New StreamWriter(writeTo)fileWriter.WriteLine(“Hello!”)

Catch fileNotFound As FileNotFoundExceptionMsgBox(“File SomeFile.txt not found.”)

Catch unauthorizedAccess As UnauthorizedAccessExceptionMsgBox(“File access denied. “ & _

“Check file and “ & _“directory write permissions.”)

FinallyIf fileWriter IsNot Nothing Then _fileWriter.Close()If writeTo IsNot Nothing Then _writeTo.Close()

End TryEnd Sub

End Class

Code sample for custom error codes:

BeforePublic Const ERR_LOG_FILE_NOT_FOUND As Integer = vbObjectError +100‘...Public Sub SomeMethod()

165

Chapter 6: Error Handling

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 165

Page 201: Professional Refactoring in Visual Basic

In other .NET languages like C# or C++ there is no equivalent for the Err object. Instead,errors are represented as exceptions, and each exception is a separate instance of someexception class. In VB .NET this is the recommended approach. It is based on the Catchexception as SomeException construct where exception represents an instance andSomeException represents an exception type.

In addition to catching exceptions, you need to deal with throwing exceptions. Here, instead of usingthe Raise method of the legacy Err object, you can use the Throw keyword in combination with anyexception type. This type might exist already, or you might need to implement your new custom excep-tion type. Either way, the exception type has to inherit System.Exception or some of its children. Thenext section takes a look at how you can deal with the simpler situation first, when you need to trans-form the raising of system errors by means of the Err object to the throwing of exceptions by means ofthe Throw keyword.

Replacing System Error Codes with Exception TypesThis refactoring is based on replacing the Err object and its Raise method with the Throw keyword. Inthe simplest form, you can replace the code

Err.Raise(53)

with this:

Throw New System.IO.FileNotFoundException

However, there is one question that you need to answer right away. How do you know that error code 53corresponds with the System.IO.FileNotFoundException class? Well, instead of some dull searchingthrough documentation in an attempt to find the real meaning of error code 53, I suggest you use the fol-lowing code to find the answer. Create a new Console Application project inside your Visual Studio IDEand write the code in Listing 6-6 inside the module.

‘...

Err.Raise(ERR_LOG_FILE_NOT_FOUND)End Sub

AfterOption Explicit OnOption Strict On

Public Class LogFileNotFoundExceptionInherits ApplicationException

End Class‘...Public Sub SomeMethod()‘...

Throw new LogFileNotFoundExceptionEnd Sub

166

Part II: Preliminary VB Refactorings

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 166

Page 202: Professional Refactoring in Visual Basic

Listing 6-6: A Simple Application Used to Find the Relation Between an Error CodeUsed with Err.Raise and an Exception Type

Module ErrorCodeToExceptionTypeSub Main()

‘Replace with number you need to transform to exceptionDim code As Integer = 53On Error Resume NextErr.Raise(53)MsgBox(Err.GetException.GetType.ToString)

End SubEnd Module

After you execute this code, you see the message box showing you the fully qualified name of the excep-tion that corresponds to the error code used in the Err.Raise statement, thanks to the GetExceptionmethod of the Err object.

In some cases, some of the other optional parameters available in the Raise method can be used. This canprovide additional information on errors such as related description, source, and help files. Fortunately,the exception type provides a roughly similar set of properties, and some other exception types provideadditional sets of properties that can be used. Take this code for example:

Err.Raise(53, “Logger”, “Main.log is missing”, _“FileNotFound.html”)

This code can be transformed along the following lines:

‘One of FileNotFoundException constructors ‘accepts Message and file name parametersDim fileNotFound As New FileNotFoundException( _ “Main.log is missing”, “Main.log”) fileNotFound.Source = “Logger”fileNotFound.HelpLink = “FileNotFound.html”‘Throw exception instanceThrow fileNotFound

The main difference here is that the exception instance has to be created explicitly. You also have to writea bit more code and establish correspondence between the Raise method optional parameters and theFileNotFoundException properties. This is not so difficult. Take a look at Table 6-1.

Table 6-1: Correspondence Between the Err.Raise Method Parameters and GenericException Properties

Err.Raise Method Optional Parameter System.Exception Property

Description Message

HelpFile HelpLink

Source Source

HelpContext None

167

Chapter 6: Error Handling

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 167

Page 203: Professional Refactoring in Visual Basic

In this specific example, the FileNotFoundException has a custom FileName property. You use informa-tion found in the legacy code (it says “Main.log is missing”) to set Main.log as a FileName propertyof the fileNotFound object. Since this property is read-only, it has to be provided as a constructor parame-ter; hence the second parameter in the FileNotFoundException constructor.

Now take a look at a bit more complex an issue and see what you can do with custom error codes.

Replacing Custom Error Codes with Exception TypesCustom errors in code are easily identified by the use of Visual Basic’s vbObjectError constant. Itmeans that the code is generally written this form: Err.Raise(vbObjectError + 100). Just in casethat constant is not present, it is worth mentioning that the range reserved for custom error codes isfrom 513 to 65535.

Now, to be fair, this refactoring is a bit difficult to complete as a preliminary refactoring, meaning as arefactoring when you have only superficial knowledge of the application problem domain. This is why,even though you are dealing with this refactoring now, this is by no means the end of the story. However,once you transform custom error codes to custom exception types, thanks to the object-oriented approachyou can treat your exception class just like any other class and improve its design as you would that ofany other class: by applying the same refactoring rules.

So imagine you need to deal with code written in this form:

Public Const ERR_LOG_FILE_NOT_FOUND As Integer = vbObjectError + 100‘...Err.Raise(ERR_LOG_FILE_NOT_FOUND)

You adopt the following approach: replace each custom error code with a new exception class. The resultingcode is as follows:

Option Explicit OnOption Strict On

Public Class LogFileNotFoundExceptionInherits ApplicationException

End Class‘...Public Sub SomeMethod()

‘...Throw new LogFileNotFoundException

End Sub

In case you need to deal with optional parameters of the Err.Raise method, the approach is similar to thatshown previously for dealing with system exception codes. It is important to note that the story of customexceptions does not end there. As you have seen, the exceptions are generally organized in the form ofinheritance hierarchies, but this additional work cannot be performed without deeper knowledge of theapplication problem domain and should be performed at some later stage. In the beginning you can stickwith the following formula:

1 error code = 1 new exception type

168

Part II: Preliminary VB Refactorings

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 168

Page 204: Professional Refactoring in Visual Basic

Later on you can organize classes into inheritance hierarchies and even reduce their number.

Bear in mind that exception classes are in the end classes just like all others. This means that they cansuffer the same smells as the other classes. Fortunately, the same refactorings that can be applied toother classes will work with exception classes as well.

Error Handling in the Rent-a-WheelsApplication

Upon examining the Rent-a-Wheels code, you can see that only the most rudimentary legacy errorhandling is in place. Because this is a typical desktop executable, a direct message to users is shownand error information is cleared from the Err object, a typical error-handling routine.

As you go through the code, you realize that exactly the same approach is adapted throughout the appli-cation. It reacts the same way no matter the type of error. No code uses Err.Number information, nor areany errors raised by the means of the Err.Raise method. See Listing 6-7.

Listing 6-7: Typical Error Handling in the Rent-a-Wheels application

Sub Main()‘“On Error Goto Label” legacy error handling in useOn Error GoTo ErrorHandler ‘some code...

‘ Message to user is shown and error information clearedErrorHandler:

MsgBox(“A problem occurred “ + _“and the application can not recover! “ + _“Please contact the technical support.”)Err.Clear()

End Sub

This simplistic error-handling approach needs to go through numerous changes before a refactoredRent-a-Wheels application sees the light of the day. Also, all error-handling code implemented so fardoes the same thing. So instead of resorting to refactorings used for the transformation of legacy tostructured error handling, you can make use of little trick with VB 2008.

Instead of refactoring error-handling code, you can remove all scattered error handling and add a singleerror handler. Thanks to the fact that in VB 2008 all errors are channeled through the UnhandledExceptionevent, you can handle this event with some error-handling code and provide the same functionality that theoriginal error-handling code did.

Application-Level Events in VB 2008In VB 2008 it is possible to handle application-level events. Events available are Shutdown, Startup,StartupNextInstance, NetworkAvailabiltyChanged, and (especially interesting for this example)UnhandledException.

169

Chapter 6: Error Handling

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 169

Page 205: Professional Refactoring in Visual Basic

In order to activate application-level error handling, do the following:

1. Open the Project Properties window. The Application tab will be displayed by default. PressView Application Events. This Visual Studio window is shown in Figure 6-4.

Figure 6-4

2. After this, the ApplicationEvents.vb file should be displayed and made visible in theSolution Explorer.

3. After that, open the ApplicationEvents.vb file in the code editor and add theUnhandledException event handler to the MyApplication class. Now you are ready to capture all unhandled errors produced by the application.

The Rent-a-Wheels code should perform the same as with original error handlers, so you can copy themessage box code to the UnhandledException handler and indicate that the application not exit by setting the ExitApplication property of parameter e to False. You can see the code you end up within Listing 6-8.

Listing 6-8: An Application-Level Unhandled Exception Treatment

Namespace My

Class MyApplicationPrivate Sub MyApplication_UnhandledException( _ByVal sender As Object, _ByVal e As Microsoft.VisualBasic.ApplicationServices. _UnhandledExceptionEventArgs) _Handles Me.UnhandledException

‘ Original message displayed to user in the case of exception

170

Part II: Preliminary VB Refactorings

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 170

Page 206: Professional Refactoring in Visual Basic

Listing 6-8: An Application-Level Unhandled Exception Treatment (continued)

MsgBox(“A problem occurred “ + _ “and the application can not recover! “ + _ “Please contact the technical support.”) ‘ Replaces Err.Cleare.ExitApplication = False

End SubEnd Class

End Namespace

Admittedly, this is not refactoring in the strictest sense. It works here because the original error-handlingcode is so simple. However, after testing the application, you will no doubt be happy with the resultsand decide that at this point replacing error-handling code with a single application-level event will dothe job in this case.

SummaryIn this chapter you started out by taking a look at a traditional way to perform error handling in VisualBasic. The legacy, unstructured way of error handling consists of using On Error GoTo Error Handler orOn Error Resume Next statements and uses error codes to signal the type of error when they are raised.

The new, structured way of error handling in VB is performed by means of structured Try–Catch–Finallyblocks and uses classes to represent error types. The new style of error handling is more in the spirit of .NETand in various ways superior to traditional error handling:

❑ It is structured, meaning it is easier to read and comprehend.

❑ Exceptions are fully fledged, extendible types and not simple codes.

❑ Using exception types makes code more .NET-interoperable.

❑ Exception filtering is easily achieved.

Because of all these benefits, you can consider the use of unstructured error handling and error codes a smell. It is a good practice to transform legacy to structured error handling. You can do this with tworefactoring transformations:

❑ Replace On Error with Try-Catch-Finally blocks

❑ Replace Error Code with Exception Type

During the process of transforming legacy error-handling code to structured Try–Catch–Finally blocks,it is a good idea to make use of the When keyword and the Err object in combination with Catch blocks tohandle conditional code that depends on error type. This step is only intermediate, as in the next step filter-ing is performed by the means of a Catch As statement in which the exception type is explicitly given.

The second part of transforming legacy error handling deals with transforming custom error codes toerror types by implementing new classes that inherit the System.ApplicationException class to rep-resent different application exceptions.

171

Chapter 6: Error Handling

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 171

Page 207: Professional Refactoring in Visual Basic

Finally, you took a look at the Rent-a-Wheels application. You used application-level events to implementa single error-handling routine for the whole application. While this is in no way a definitive solution forexception handling in Rent-a-Wheels, it can help postpone work on error handling for some more auspi-cious moment, when the whole design of the application starts to take its final form.

This completes the investigation of error handling in VB .NET. The story about error handling does notend here. Still, the good news is that from now on you can treat exceptions just as you would any otherclass in your code. Now it’s time to pursue further preliminary refactorings in the next chapter by lookingat some everyday code hygiene.

172

Part II: Preliminary VB Refactorings

79796c06.qxd:WroxPro 2/23/08 8:15 AM Page 172

Page 208: Professional Refactoring in Visual Basic

Basic Hygiene: El iminatingDead Code, ReducingScope, Using Expl ic it

Impor ts, and RemovingUnused References

In this chapter you’ll continue to investigate preliminary VB refactorings. As I have already men-tioned, you can generally perform these refactorings without any deeper understanding of theapplication problem domain, and they are mostly performed on the syntactic level. You’ll usethem to prepare the code for more complex restructuring where application domain knowledge is indispensable.

❑ The first issue you’ll deal with in this chapter is dead code. Dead code comprises those sectionsof code that are left unused after some modification has been performed: a new feature isimplemented, a bug resolved, or some refactoring performed. Dead code can have very neg-ative effects on maintainability, and you will see the reasons why it should be eliminated.

❑ After that, I’ll remind you of the importance of well encapsulated code. Don’t forget,encapsulation is a pillar of well-constructed object-oriented code. You will take a look atelement scope and access level as mechanisms for hiding information and implementa-tion details in your code.

❑ The next topic I’ll deal with is that of explicit imports. You can reference elements from other namespaces by using fully qualified names in code or by using the Imports state-ment. I will compare both styles and will explain why I prefer using the Imports statement.

❑ The last issue I’ll deal with in this chapter is that of unused assembly references. It is agood practice to eliminate these unused references as they appear, and you will see howthe Visual Studio can help you do it.

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 173

Page 209: Professional Refactoring in Visual Basic

Part II: Preliminary VB Refactorings

174

With this, you will finish the exploration of preliminary refactorings. I hope that after you finish readingthis chapter you will have a clear picture of code cleanness and hygiene, and of how to perform transfor-mations to bring into shape any code that does not respect the rules.

Eliminating Dead Code

In the first chapter I mentioned that code simplicity is one of most valuable qualities your code can possess.Each additional symbol in the source means an additional effort that has to be spent on code assimilation.Programmers often have the attitude that more cannot hurt, and they keep the code somewhere inside thecurrent code base just in case. However, code that that never gets executed can be the biggest riddle for aprogrammer who didn’t author that code. That programmer may reason: “code exists, so it must servesome purpose. Because no purpose is evident, full code comprehension has not been reached yet.” So theprogrammer ends up spending more effort on testing, debugging, and profiling in an attempt to discoverthe lost meaning of the code.

Definition: Dead code is redundant, inoperative code that is not executed under any circumstances.

Smell: Dead CodeDetecting the SmellUse a compiler to find unused code. You do this by commenting suspicious code andrebuilding the project. If no new errors are reported by the compiler, you have poten-tially discovered a new case of dead code. Now you need to inspect the use at runtimeof this possibly dead code.

Use a code coverage tool (we mentioned some of these in Chapter 3) to find potentiallydead code. Execute a comprehensive suite of tests with the coverage tool activated.Analyze coverage tool results and look for sections of code that were never executed. If the code that wasn’t executed is the same as the code that was not reported missingby the compiler, it is quite probable that you have discovered dead code. The accuracyof this methodology depends heavily on the level of coverage your tests provide.

Use a simple text search to find commented blocks of code. Commented code nevergets executed; the information it contains is generally historic and should not form partof the code base. Such information is best kept by your source-code repository.

Related RefactoringUse Eliminate Dead Code refactoring to eliminate this smell.

RationaleDead code increases the complexity of your code. It makes your code more difficult tounderstand and maintain; further, this code has no value. Since it is never used, it isnever executed. Dead code can result in unnecessary effort and can obscure the origi-nal intent and design of your code.

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 174

Page 210: Professional Refactoring in Visual Basic

Chapter 7: Basic Hygiene

175

The most common sources of dead code are maintenance work and design changes. Maybe you haveerased a line or two, and before you know it, somewhere a whole method is left unused. Or you mayhave added a certain condition during debugging in order to test a specific block of code, and forgottento remove the condition before going into production. Maybe you copied the method and then com-mented a version of the method you changed, instead of removing the unused method. Whatever theorigin of dead code, keeping it inside your code base is counterproductive and such historic informationshould be left for the version-control system to keep.

Types of Dead CodeYou can distinguish the following types of dead code:

❑ Unreachable code: This is the code that you know with absolute certainty will never be executed.It is still considered by the compiler, which reports any syntax error or the like. Your first instinctis to resolve the compiler error, but in this case that’s a complete waste of time and effort.

❑ Commented code: These are ignored by the compiler, but can still cloud the programmer’s vision.If a bug is present in code related to the commented code, the programmer will inevitably start tosearch for a solution in the commented section, looking for the reason the code was commentedand not removed.

❑ Unused code: This code is present if you are dealing with some type of reusable library. Forexample, you have a method or a class that you suspect is never used. You cannot be sure of this because you do not have the complete code base inside your IDE. In such a case, the element should be phased out gradually. In the first version, make your clients aware that infuture versions this element will be eliminated by marking it with ObsoleteAttribute.

Listing 7-1 shows some examples of the different flavors of dead code.

Listing 7-1: Flavors of Dead Code

Option Explicit OnOption Strict On

‘XmlDocument never referencedImports System.Xml.XmlDocument

Public Class DeadCodeDemo Shared Sub Main()

Dim number As Integer = 5‘Never evaluates to True, following line is never reachedIf number < 4 Then

MsgBox(“Destined never to show, unreachable”)End If‘Redundant code, “number” variable value already 5number = 5 ‘ Exits the method, following line is never reachedExit Sub MsgBox(“Destined never to show, unreachable”)

End Sub

‘ Method is private and never used in the class it belongs to - unreachable

Continued

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 175

Page 211: Professional Refactoring in Visual Basic

Part II: Preliminary VB Refactorings

176

Listing 7-1: Flavors of Dead Code (continued)

Private Sub NotUsedInClassItBelongsTo() MsgBox(“Destined never to show, unreachable”)

End Sub

‘ Public method is never usedPublic Sub NotUsed()

MsgBox(“Destined never to show, unused”)End Sub

‘Commented method is never used‘Public Sub Commented() ‘ MsgBox(“Destined never to show, commented”)‘End Sub

End Class

Common Sources of Dead Code

Refactoring: Eliminate Dead CodeMotivationMore code means more complexity. By eliminating unnecessary code from your codebase, you are making it easier to read, comprehend, and maintain.

Related SmellsUse this refactoring to eliminate the Dead Code smell.

MechanicsRefer to the Dead Code smell definition earlier in the chapter to learn how to locateoccurrences of dead code. After you have identified dead code, eliminate it. Commit the version to a version-control system so your latest action can be easily traced and a backup preserved.

In case you are working with reusable code like library or component code and you are suspicious that some code element is unused, start by marking this element withObsoleteAttribute. Remove the element completely in the version to follow.

BeforeOption Explicit OnOption Strict On

Imports System.Xml

Public Class Customer‘Import obsolete, since ToXml method commentedPrivate mFirstName As StringPrivate mLastName As StringPrivate mSSN As String

Public Property FirstName() As String

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 176

Page 212: Professional Refactoring in Visual Basic

Chapter 7: Basic Hygiene

177Continued

GetFirstName = mFirstName

End GetSet(ByVal value As String)

mFirstName = valueEnd Set

End Property

Public Property LastName() As StringGet

LastName = mLastNameEnd GetSet(ByVal value As String)

mLastName = valueEnd Set

End Property

Public Property SSN() As StringGet

SSN = mSSNEnd GetSet(ByVal value As String)

mSSN = valueEnd Set

End Property

‘ Whole method commented‘Public Function ToXml() As XmlDocument ‘ Dim doc As XmlDocument = New XmlDocument‘ Dim xml As String‘ xml = “<Customer>” & _‘ “<FirstName>” & mFirstName & “</FirstName>” & _‘ “<LastName>” & mLastName & “</LastName>” & _‘ “<SSN>” & mSSN & “</SSN>” & _‘ “</Customer>”‘ doc.LoadXml(xml)‘ Return doc‘End Function

End Class

AfterOption Explicit OnOption Strict On

Public Class Customer

Private mFirstName As StringPrivate mLastName As StringPrivate mSSN As String

Public Property FirstName() As String

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 177

Page 213: Professional Refactoring in Visual Basic

Part II: Preliminary VB Refactorings

178

There are a number of ways you can end up with some dead code inside your code base. I will mentionsome I find to be very common so you can prepare yourself and be ready to look for them.

Detached Event HandlerThe event is never fired, but the event code is still present. This happens very often if you decide toeliminate a control from the form that previously served some purpose. All related event-handlingcode is still preserved and needs to be removed manually.

Invisible Control Sometimes you place control on the form and it gets covered by another control, or it is resized in such away it is not visible anymore. Or maybe it is set invisible or disabled right from the beginning and nevergets used. Eliminate such controls and all related event-handling code.

Imports Section Importing Unused ElementsI come across obsolete elements in import sections very often. Once you eliminate a property or a method,or move some code between classes, it is only too easy to forget to remove import statements that weremade redundant by these changes. It may seem innocuous, but this code can have some far-reaching con-sequences. When you are performing some large-scale refactoring, it is very important to understand howdependencies work out in your code. When you are performing some fast code browsing, you often baseyour conclusions on the content of the Imports section. A few redundant Imports statements and you canbe completely misled.

GetFirstName = mFirstName

End GetSet(ByVal value As String)

mFirstName = valueEnd Set

End Property

Public Property LastName() As StringGet

LastName = mLastNameEnd GetSet(ByVal value As String)

mLastName = valueEnd Set

End Property

Public Property SSN() As StringGet

SSN = mSSNEnd GetSet(ByVal value As String)

mSSN = valueEnd Set

End PropertyEnd Class

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 178

Page 214: Professional Refactoring in Visual Basic

Chapter 7: Basic Hygiene

179

Ignored Procedure Return ValueThis happens when the return value is ignored upon the function’s being called. It means that the clientsare not really interested in the information the return value provides. This function should be transformedto a sub, and code in charge of returning the value should be eliminated.

Ignored Procedure Return ParameterWhen the function is called, the return parameter value is ignored. Again, if this is the case with all functionclients, the ignored return parameter and related code should be eliminated.

Local Variable Is Not ReadThe variable can be assigned, but if it is not read, there is no use for it. In fact, it is just another case ofdead code. Eliminate the variable in question.

Read-Only or Write-Only Property Containing Both Get and Set Property Procedures

After you write down the property declaration, Visual Studio will automatically create both Get and Setproperty procedure stubs. Very often programmers, from force of habit, will leave and implement bothprocedures, even though the property is supposed to be read- or write-only.

Obsolete Elements With time, as more and more changes are applied, some bigger elements in your code can be left unreach-able or unused. Such elements can be classes, enumerators, interfaces, modules, and even whole name-spaces. These elements should be eliminated.

Don’t have any mercy on dead code. Be sure to use some versioning system as a backup and eliminatedead code without fear. You will soon feel as if you have left some burden from the past behind. Yourcode base will soon become much clearer, and easier to comprehend and maintain.

Reducing the Scope and Access Level of Unduly Exposed Elements

Continued

Smell: OverexposureDetecting the SmellIf you are suspicious of a certain element for having an unnecessarily broad access level,reduce the access level by one degree and then build the project. If no error is reported bythe compiler, you have found an overexposed element. The element can be any of the fol-lowing: an interface, module, class, structure, structure member, procedure, property,member variable, constant, enumeration, event, external declaration, or delegate.

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 179

Page 215: Professional Refactoring in Visual Basic

Part II: Preliminary VB Refactorings

180

You have often heard that encapsulation is the first pillar of object orientation. Encapsulation is applied toconceal internal details of our code from the public eye. This is generally referred to as information andimplementation hiding. In order to reduce the complexity of systems, you often resort to decomposing thesystem into different units. When you are following the “divide and conquer” principle, it is very impor-tant to hide as much internal information and implementation detail of an individual module as possible.

In VB .NET, the largest organizational unit is the assembly. In order to use services that some assemblyprovides, you need only the compiled binary. You interact with the assembly through its interface — a setof publicly visible elements. Having this interface as simple and thin as possible greatly simplifies yourinteraction with the assembly. You need not have any notion of the internal workings of the assembly.

So how does this work out in practice? I will try to illustrate this with an example. Take a look atFigure 7-1. The difference in size between the ShippingCost and CartPersistence interfaces can be observed visually.

Imagine you have a system consisting of three assemblies: ShoppingCart, ShippingCost, and CartPersistence. You are working on the ShoppingCart assembly, and you are using the ShippingCost assembly services to calculate the cost of shipping for items in the cart. Using theShippingCost assembly proves to be quite simple: a single interface and a single operation are exposedto you as a client.

Now you need to write some test code in which you use CartPersistence services also. However, thisis proving to be much more complex. The CartPersistence interface exposes a myriad of implementa-tion details related to database communication, transaction management, logging, and so on — a numberof details a client using this interface should not have to worry about. As a matter of fact, there is no rea-son why you should have any notion of the underlying persistence mechanism. The persistence mecha-nism could just as well use a simple file, Excel spreadsheet, or whatever. There is no reason why anyCartPersistence client should be aware of its inner workings. In this case, while interacting withCartPersistence, you are unnecessarily bogged down with overexposure of assembly internals.

As a rule of thumb, keep the scope and access level of programming elements as restricted as possible.

You suspect that a certain element has too broad a scope, even when a minimum accesslevel is specified. Move that element to a more enclosed region and build the project. Ifno error is reported by the compiler, you have successfully identified an element withan unnecessarily broad scope.

Related RefactoringUse Reduce Access Level and Reduce Scope refactoring in order to eliminate this smell.

RationaleUnnecessary exposure of internal and implementation details goes against the basic prin-ciples of encapsulation and data and information hiding. It makes your code less modular,more complex, and difficult to use and maintain. Dependencies can freely spawn in such a system, making it in fact monolithic. An unnecessary level of detail is exposed in such away that it complicates use of the code units.

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 180

Page 216: Professional Refactoring in Visual Basic

Chapter 7: Basic Hygiene

181

Figure 7-1

The benefits of encapsulation and information and data hiding can be easily appreciated. Even comparingthe size of the two interfaces shown in Figure 7-1 visually gives us a lot to talk about. Well-encapsulatedunits are easy to use and interact with, eliminating unnecessarily complexity and providing good modu-larity of the system.

In VB you can use the scope and access level to control the level of exposure of programming elements.By minimizing the exposure of programming elements, you will successfully encapsulate and hideimplementation details of your code.

Scope and Access LevelThe scope (or visibility) of a declared element is the region of code that can access that element withoutfully qualifying it. It means that for the element, this region is its closest neighborhood, where every-one is known by their first names. It also has one very practical consequence: such elements are read-ily displayed by IntelliSense. Elements out of scope have to be imported in order to be displayed byIntelliSense directly. If you do not import the element, IntelliSense will still help you find it, but only ifyou follow the whole path of the fully qualified name. In Figure 7-2 you can see how IntelliSense willhelp you reference an out-of-scope element if you use the element’s fully qualified name.

Figure 7-2

ShoppingCart ShippingCost<<interface>>ShippingCost::IShippingCost+CalculateCost()

<<interface>>CartPersistence::ICartPersistence+Save()+Update()+Delete()+Retrieve()+OpenConnection()+CloseConnection()+BeginTransaction()+RollbackTransaction()+ConnectionString()+ConnectionTimeout()+LogError()

CartPersistence

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 181

Page 217: Professional Refactoring in Visual Basic

Part II: Preliminary VB Refactorings

182

ScopeIn VB, we differentiate four levels of scope:

❑ Block scope

❑ Procedure scope

❑ Module scope

❑ Namespace scope

The scope first and foremost depends on the enclosing region in which you declared the element —meaning the block, procedure, VB module, class, and structure. For example, if you declare a variableinside the method, it will be available throughout that same method, but it will not be available fromany other method in that or any other class.

In case that element is declared on the module level (VB module, class, structure), element scope is furtheraffected by access level. If a variable is declared as private, it will have module scope, while if the friend orpublic access level is used, the element will be visible throughout the namespace (namespace scope).

Access LevelAccess level controls which code can access (read or write) a declared element. In VB, you differentiatethe following access levels:

❑ Private

❑ Friend

❑ Protected

❑ Protected friend

❑ Public

Access level is controlled by corresponding access modifiers when the element is declared. In some cases,the access level of element also depends on the access level of the containing structure. For example, youwill not be able to access a method declared as public from another assembly if the method containing theclass is marked as friend.

Common Sources of Overexposure

Refactoring: Reduce Access LevelMotivationWell-encapsulated code keeps internal and implementation detail well hidden from outside view. This way, thanks to the modular approach, the complexity of the system asa whole is reduced. Encapsulation will also help control dependencies in the system.Other benefits of reduced access level are lower risk of name clashes and simplifiedsecurity management.

Related SmellsUse this refactoring to eliminate the Overexposure smell.

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 182

Page 218: Professional Refactoring in Visual Basic

Chapter 7: Basic Hygiene

183

As is common with many smells, change is the easiest path to overexposure. After some reshuffling ofcode, you might get in a situation where the scope and access level of some elements should be reduced,because they are now referenced from a more enclosed area. Unfortunately, this step is frequently omitted.

On the other hand, overexposure can be the result of simple oversight. The benefits of encapsulation anddata and information hiding are not easy to see in the short term or on a small scale, and programmersoften don’t give them due attention.

MechanicsStart by reducing the access level by one degree and build the project. If the compile is successful, repeat this step until an error is reported by the compiler or the privateaccess level is reached. In case of error, return to the most reduced access level underwhich the project still compiles successfully.

BeforeOption Explicit OnOption Strict On

Public Class Customer‘This field need not be publicPublic mFirstName As String

Public Property FirstName() As StringGet

FirstName = mFirstNameEnd GetSet(ByVal value As String)

mFirstName = valueEnd Set

End Property‘...

End Class

AfterOption Explicit OnOption Strict On

Public Class Customer‘Access level reduced from public to privatePrivate mFirstName As String

Public Property FirstName() As StringGet

FirstName = mFirstNameEnd GetSet(ByVal value As String)

mFirstName = valueEnd Set

End Property‘...

End Class

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 183

Page 219: Professional Refactoring in Visual Basic

Part II: Preliminary VB Refactorings

184

Decision and Control Structures in VB .NET Are ScopedIn VB .NET, there is a new type of variable scope. The block scope was not present in VB6 and priorversions of Visual Basic, so it is often overlooked by traditional VB programmers. In VB6 and earlier it was considered a good practice to declare all the variables at the beginning of the procedure. In VB.NET, however, you can keep your code even more tightly encapsulated.

While block-level variables have block-level scope and can be accessed only from within that same block,they have the lifetime of the procedure they belong to. This means that if the block is entered multipletimes during the procedure execution (For and While loops, for example), the variable will retain itsvalue between block executions unless it is initialized inside the block.

Take the code in Listing 7-2 for example.

Listing 7-2: Block-Level Scope

Public Shared Sub Main()For blockLevel As Integer = 1 To 10

MsgBox(“Iteration: “ & blockLevel.ToString)Next blockLevel

‘Compiler error is reported on following lineMsgBox(“Last Iteration: “ & blockLevel.ToString)

End Sub

If you try to compile this code, the compiler reports the following error: “Name ‘blockLevel’ is notdeclared.” The variable blockLevel is accessible only in the region between For and Next statements.

Visual Studio Adds Class with Public Access Level by DefaultIf you use the Add Item option and then select Class in Visual Studio, the IDE will add a file to the proj-ect. However, this file is not completely empty. The IDE will also generate a class declaration skeletonand add it to a file. As a matter of fact, you have already learned how Visual Studio templates work andhow they can be modified in Chapter 3. The Class template and templates for other types of items comewith a predefined public-access level. It is all too easy to leave the default access level unmodified, evenif this class is not meant to be exposed to the outside world.

Quick Fix for Design ErrorsIn some situations, a quick solution to some design-related problems can lead to intentional exposure ofsome private element. While this can give some fast short-term results, a hefty price will almost certainlybe paid later on. Take a look at the code in Listing 7-3.

Listing 7-3: Quick Fix Requires the Exposure of a Private Connection Object

Option Explicit OnOption Strict On

Imports System.Data.SqlClient

Public Class ProductDetailPersistence

Private connection As SqlConnection

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 184

Page 220: Professional Refactoring in Visual Basic

Chapter 7: Basic Hygiene

185

Listing 7-3: Quick Fix Requires the Exposure of a Private Connection Object (continued)

‘...Public Sub Delete()‘...End Sub‘...

End Class

The class ProductDetailPersistence is, as the name suggests, responsible for the persistence ofproduct detail. At some point during development, the developers realize that a very important omis-sion was made. No provision was made for transactional behavior of the class. Some operations, forexample product elimination, have to be performed under the same transaction as product-detail elimi-nation. The quick fix is to expose the connection object by making it public and thus providing a com-mon transactional context for both operations.

Naturally this presents a problem, because low-level persistence implementation details are exposed to aclient. So if at some point the decision is made to change the underlying data store, the changes cannot beisolated to a persistence class only. Because all clients have knowledge of SqlConnection, any attempt toreplace the underlying data store with some other type of persistence mechanism or some other databasewould cause a chain reaction of changes in all client code.

Continued

Refactoring: Move Element to a More Enclosed RegionMotivationKeeping the scope of elements at a minimum helps make your programs better encap-sulated, more modular, more robust, and easier to maintain, read, and use. Other bene-fits are optimized memory management, simplified security, and so on.

Related SmellsUse this refactoring to eliminate the Overexposure smell.

MechanicsThis refactoring should be preceded by Reduce Access Level refactoring, whereapplicable. Select the element for scope reduction and move its declaration to a moreenclosed region.

BeforeOption Explicit OnOption Strict On

Public Class CustomerXmlWriter

‘Variable doc declared on class level, but used in ToXmlmethod only

Private customer as CustomerPrivate doc As System.Xml.XmlDocument _ = New System.Xml.XmlDocument

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 185

Page 221: Professional Refactoring in Visual Basic

Part II: Preliminary VB Refactorings

186

Dealing with OverexposureOnce you have identified an overexposed element, dealing with overexposure is relatively simple. Thefirst step should be access-level reduction. Reduction should be performed gradually: you should rebuildthe project after each reduction until a compiler error is produced or you reach the private access level. If a compiler error is produced, the latest reduction should be undone. In Table 7-1, you can see the gradualpath of access-level reduction.

‘...Public Function ToXml() As System.Xml.XmlDocument

Dim xml As Stringxml = “<Customer>” & _“<FirstName>” & customer.mFirstName & “</FirstName>” & _“<LastName>” & customer.mLastName & “</LastName>” & _“<SSN>” & customer.mSSN & “</SSN>” & _“</Customer>”doc.LoadXml(xml)Return doc

End FunctionEnd Class

AfterOption Explicit OnOption Strict On

Imports System.Xml

Public Class CustomerXmlWriterPrivate customer as Customer‘...

Public Function ToXml() As XmlDocument ‘doc variable declaration moved to ToXml method Dim doc As XmlDocument = _ New XmlDocument Dim xml As Stringxml = “<Customer>” & _“<FirstName>” & customer.mFirstName & “</FirstName>” & _“<LastName>” & customer.mLastName & “</LastName>” & _“<SSN>” & customer.mSSN & “</SSN>” & _“</Customer>”doc.LoadXml(xml)Return doc

End FunctionEnd Class

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 186

Page 222: Professional Refactoring in Visual Basic

Chapter 7: Basic Hygiene

187

Table 7-1: Gradual Access-Level Reduction

A similar pattern can be applied to scope reduction. This step should follow access-level reduction. Here,the location of the element declaration is changed in such way that scope is reduced to the minimal levelnecessary. Table 7-2 illustrates the scope-reduction path.

Table 7-2: Gradual Scope Reduction

When you are talking about access level and scope, less is truly more. If you are writing code, it pays tobe secretive. Make a habit of declaring all your elements under the minimum scope and access level.This can often result in better designed and better encapsulated code.

Using Explicit Impor ts

Continued

Smell: Using Fully Qualified Names Outside the Imports SectionDetecting the SmellUnfortunately, you need to resort to scanning the source visually in order to detectthis smell. There is, however, a step you can perform in order to speed up the search.Temporarily remove all project references. Also, comment the Imports section tem-porarily so it is not marked with an error sign by the compiler. The compiler will nowmark all the declarations in which types from external assemblies were used. All thatis left is to review each declaration in a search for fully qualified names in the body ofthe code.

Related RefactoringUse Explicit Imports refactoring in order to eliminate this smell.

Current Reduce to

No namespace Namespace

Namespace Module (class, structure)

Module (class, structure) Procedure

Procedure Block

Current Reduce to

Public Protected friend

Protected friend Friend

Friend Private

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 187

Page 223: Professional Refactoring in Visual Basic

Part II: Preliminary VB Refactorings

188

The issue of explicit imports might not seem major at first. After all, imports are there only to help youwrite less, so you can reference an element (namespace, interface, module, class, structure, and so on)using its simple name instead of its fully qualified name. It will not absolve you from adding references toa project. So why attach any importance to it? If the effect is the same, why not let a programmer use fullyqualified names inside the body of an element? I will try to answer these questions in the next section.

Imports Section Depicts Dependencies in Your System

Refactoring: Replace Fully Qualified Names with Explicit ImportsMotivationBy adding an Imports statement you will reduce the quantity of the code you have towrite. Using imports is much more efficient than repeatedly writing fully qualifiednames in your code.

By consistently using an Imports section you will provide easy-to-find, reliable infor-mation related to dependencies in your code.

Related SmellsUse this refactoring to eliminate the Using Fully Qualified Names Outside ImportsSection smell.

Mechanics1. After identifying the fully qualified name in body of the module, add the

Imports statement for this particular name, if it is not already present inthe Imports section.

2. Replace the recently identified fully qualified name in the body of themodule with the simple name.

BeforeOption Explicit OnOption Strict On

Public Class CustomerXmlWriter

RationaleUsing fully qualified names in the body of the code module1 (Class, VB Module,Structure, Interface, and so on) will make your code more difficult to read and write.Fully qualified names can be very long and using them is plain tedious. It is much bet-ter to have them imported in a single place.

The Imports section is a very good place to look when trying to understand large-scale design and system dependencies. Using the Imports section inconsistently andresorting to fully qualified names in the body of the module can give a false impressionwhen you are inspecting the dependencies in your code.

1Here I use the broader meaning of the word “module,” meaning a form of code grouping, and itcan indicate interface, VB module, class, structure, and so on.

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 188

Page 224: Professional Refactoring in Visual Basic

Chapter 7: Basic Hygiene

189

As projects grow in size and more people take part in development, large-scale issues like “dependencies”grow in importance and become more apparent. The natural way to deal with large-scale projects is toorganize software into separate pieces. This way, smaller groups of programmers can dedicate themselvesto constructing a single piece of a puzzle. Because you generally start out with the blueprint showing howthese pieces should fit in, construction of the final product is just a matter of putting them together. In.NET, these largest organizational units are identified as assemblies.

However, if special care is not taken, these pieces start to develop unpredicted and undesired dependenciesbetween each other. Growing new dependencies is as easy as adding one project reference and one Importsstatement in your code.

Private customer as Customer‘...Public Function ToXml() As System.Xml.XmlDocument

Dim doc As System.Xml.XmlDocument = _ New System.Xml.XmlDocument Dim xml As Stringxml = “<Customer>” & _“<FirstName>” & customer.mFirstName & “</FirstName>” & _“<LastName>” & customer.mLastName & “</LastName>” & _“<SSN>” & customer.mSSN & “</SSN>” & _“</Customer>”doc.LoadXml(xml)Return doc

End FunctionEnd Class

AfterOption Explicit OnOption Strict On

‘“Imports”statement addedImports System.Xml

Public Class CustomerXmlWriterPrivate customer as Customer‘...‘ XmlDocument simple name usedPublic Function ToXml() As XmlDocument

Dim doc As XmlDocument = _ New XmlDocument Dim xml As Stringxml = “<Customer>” & _“<FirstName>” & customer.mFirstName & “</FirstName>” & _“<LastName>” & customer.mLastName & “</LastName>” & _“<SSN>” & customer.mSSN & “</SSN>” & _“</Customer>”doc.LoadXml(xml)Return doc

End FunctionEnd Class

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 189

Page 225: Professional Refactoring in Visual Basic

Part II: Preliminary VB Refactorings

190

Imagine you have finished working on your class. The morning after, you find out it’s not workinganymore because a class you depend upon has unexpectedly changed. The other programmer wasnot aware that your class depends on his or her class, and decided to apply changes as he or shedeemed necessary.

Imagine another scenario. Your assembly deals with a business domain but is also concerned with datapersistence. For that purpose, it uses different assemblies for XML or database-type persistence. However,in this specific application, you have reused your assembly for its business-domain functionality, eventhough you do not need any persistence functionality. Nevertheless, XML and database-persistence assem-blies still have to be deployed to go along with your assembly because your assembly depends on them.

This is just the tip of the iceberg of dependency-related problems. You will see more of them as youplunge into some more complex issues related to large-scale design later on in the book. Even with thisshort introduction, the significance of correct dependency management should be apparent.

The first and very natural attempt to comprehend the reality of dependencies in your project is to visu-ally scan the Imports sections of source-code files. In some large-scale projects scanning the whole codebase is not only impractical, but can be close to impossible. And available documentation and diagramscan often be out of sync with the real world — more showing good intentions than really representingthe current state of affairs.

In VB .NET you can import the namespace element or the whole namespace. If you import the wholenamespace, you will have less code to write, but import details will not be immediately visible. Youshould choose only one style and stick with it throughout the project.

Consider the following anecdote: An acquaintance of mine had to perform a lot of large-scale refactoringwork in order to reorganize the system and finally eliminate some dependencies from certain assemblies.After he thought he had finished the task, he triumphantly removed the external assembly reference fromthe project and pressed the build button. As a result, a list of compiler errors appeared in the IDE, andhe discovered a number of places in which external assembly types were referenced by means of fullyqualified names. He based his assumptions on the Imports section. He found that because of the freeuse of fully qualified names throughout the code, a number of places in which external types were refer-enced were not identified from the start.

While there are tools that can help you with the task of comprehending dependencies in your code, theyare not always available. That’s why maintaining the Imports section as current and avoiding the useof fully qualified names in the body of the modules is very important in the attempt to ensure your codeis readable and easy to maintain.

One last detail regarding the namespace imports. It is also possible to import a namespace globally on aproject level. For this, you need to go to the References tab on the Project Properties page. As you might havealready guessed, I am not a big fan of importing namespaces globally. While it might not hurt if you importnamespaces belonging to the .NET framework, you can always count on them to be available, so using thisoption on other namespaces can be the source of some unwarranted surprises in the same way as the usageof fully qualified names. In Figure 7-3 you can see how a namespace is globally referenced in a project.

Definition: One module is dependent on another if change in that other module canprovoke changes in the first.

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 190

Page 226: Professional Refactoring in Visual Basic

Chapter 7: Basic Hygiene

191

Figure 7-3

Removing Unused Assembly References

Smell: Unused ReferencesDetecting the SmellYou should check for the presence of unused references after any major and large-scale refactoring. Open the References tab on the Project Properties page and press the Unused References... button. Visual Studio will search for and display the list ofunused references.

Related RefactoringUse Remove Unused References refactoring to eliminate this smell.

RationaleAn unused reference is a breeding ground for unnecessary dependencies. Dependencieshave a great influence on the design of your system from a large-scale “bird’s-eye” view.A reference is a signal to programmers that they can use services that a certain assemblyprovides, and thus an otherwise unnecessary dependency is established.

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 191

Page 227: Professional Refactoring in Visual Basic

Part II: Preliminary VB Refactorings

192

While the VB compiler is intelligent enough not to include unused references in the assembly manifest,maintaining unused assembly references in your project still has a serious downside. Adding a referenceto an external assembly is a significant decision from a large-scale design viewpoint and should be madewith great care.

Having a dependency set in your project is a sort of a green light telling developers that they are free touse the services that the referenced assembly provides. In some cases, especially after some large-scalechanges to the code base, the reference is made obsolete, and dependencies on the referenced assemblyhave to go to some other place. The design has changed, and the project should not depend on the refer-enced assembly any more. However, if the reference from the project properties is not removed also,there is a good chance that this assembly will get used again at some point. In a way, it is only logical.“Why do we have a reference to an assembly if we are not meant to use it?” any developer might right-fully ask. In order to avoid this kind of confusion, avoid unused references and remove them as soon asthey appear.

Fortunately, in Visual Studio 2005 finding and removing unused references is very easy. Visual Studio 2005boasts a “Remove Unused References” feature, and you can even see the Unused References... button inFigure 7-3, which shows the References tab in the Project Properties window.

Basic Hygiene in the Rent-a-WheelsApplication

Thanks to the early age of the Rent-a-Wheels application, the smells described in this chapter were notrife. The changes we made amounted to removing a few commented methods and a few detached eventhandlers. Listing 7-4 shows one detached event handler that was eliminated after we performed basichygiene refactorings on the Rent-a-Wheels application.

Listing 7-4: Detached Event Handler in the Rent-a-Wheels Application Was Eliminated

Private Sub Button1_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Button1.Click

Refactoring: Remove Unused ReferencesMotivationHaving strict policies on controlling references and removing unused referencesquickly will help keep dependencies at bay in your project.

Related SmellsUse this refactoring to eliminate the Unused References smell.

MechanicsSelect the References tab in the Project Properties window. Open the Unused Referencesdialog by pressing the Unused References... button. Visual Studio will search for unusedreferences and display them. Remove unused references by pressing the Remove button.

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 192

Page 228: Professional Refactoring in Visual Basic

Chapter 7: Basic Hygiene

193

Listing 7-4: Detached Event Handler in the Rent-a-Wheels Application Was Eliminated (continued)

Dim oCn‘Test SqlConnectionoCn = New SqlConnection(“Data Source=TESLA-DAN;” + _“Initial Catalog=RENTAWHEELS;User ID=sa”)oCn.Open()

End Sub

SummaryThis chapter showed some very common cases of poorly maintained, unhygienic code. In such cases rotwill easily take hold, so you need effective methods to identify and eliminate it.

Dead code can often be a source of confusion and additional complexity. There are different ways fordead code to appear — as commented sections, unused event handlers, removed controls, ignoredmethod parameters or return values, and so on. Whatever the form, dead and redundant code shouldbe eliminated without exception.

Very often programmers unintentionally give unnecessarily broad scope and visibility to certain pro-gramming elements. To construct well-encapsulated code, it is important that you adhere to the princi-ples of information/implementation hiding. Exposing unnecessary internal detail goes against thisprinciple, and in such cases you can improve encapsulation by reducing the scope and access level ofoverexposed elements.

Often, when reading the code, you rely on the Imports section to understand dependencies in yourcode. Using fully qualified names instead of relying on the Imports section can make comprehendingthe dependencies in your code much more difficult. Performing imports explicitly should be favoredover using fully qualified names in the body of the code.

Each existing reference will tell the programmer that a referenced library is meant to be used from the current library. Problems occur when obsolete references are not removed, because such referencesencourage programmers to freely make use of the referenced libraries. It is a good practice to removeunused references, and Visual Studio has a feature that can help you do this.

The next chapter will deal with standard refactoring techniques. These refactorings cannot be performedwithout a good understanding of code and problem domain. They can have a profound impact on theway your code is designed; dealing with them means dealing with core issues related to code quality. I hope that the preliminary refactoring in this chapter served as a good warm-up for the more complexwork that lies before us.

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 193

Page 229: Professional Refactoring in Visual Basic

79796c07.qxd:WroxPro 2/25/08 9:01 AM Page 194

Page 230: Professional Refactoring in Visual Basic

Part III: Getting Started with Standard RefactoringTransformations

In Part III you will start out with some of the most basic and most common refactor-ings. These often do not span more than one class, and while they are very importanton their own, eliminating some unpleasant and very basic smells, they are also usedto prepare the code for some more complex refactorings.

In this part you will also witness how a direct link between the source code and theproblem domain is being established. That is why I will start out with a few words onobject-oriented analysis, a discipline that can help you understand well the problemyour application is meant to solve. You will learn why the right choice of names incode is so important.

Next you will read about an important distinction between public and published inter-faces and about this distinction in the context of Rename refactoring. This matterhas an important bearing on the rest of the refactorings and will be of consequencethroughout the book, because you are learning how to perform refactoring safely.

Next, this part turns to the basic structuring of code, and you will learn how to performan Extract Method refactoring, one of the most commonly used types, and see how ithelps you deal with duplication in your code and improves reuse. You will see howsome other related refactorings, like Replace Temp with Query or Inline Temp (DA),work, why using literal values liberally in your code is not such a good idea, and seesome other smells and refactorings.

By the end of this part, you should be able to see how the contours of the object-oriented design of the sample Rent-a-Wheels application are slowly but unques-tionably beginning to appear.

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 195

Page 231: Professional Refactoring in Visual Basic

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 196

Page 232: Professional Refactoring in Visual Basic

From Problem Domain toCode: Closing the Gap

Programmers have in one sense a unique profession. It’s their job to analyze and understand the busi-ness of others. Often they gloat over speed and algorithm effectiveness, forgetting that a program isonly as good as the extent to which it fulfills its requirements and the client’s needs. Unless you areone of those programmers who prefer counting processor cycles to making your software purposefuland useful to others, you will agree that understanding the problem domain of your application is thefirst and most critical step in the process of software development.

This process is by no means trivial. You have to guide your clients in discerning the essential require-ments from those that are superficial, in defining the rules of the business, and sometimes even ineducating your clients about proven solutions in software design. While you are at it, you have tolearn yourself and accumulate a compendium of knowledge on the problem domain you are dealingwith. Finally, you use this knowledge not only to make your application work properly and fulfilluser requirements, but also to make your code meaningful, establishing the links connecting theproblem domain, the design, and the source code.

In this chapter you will see how these links are established.

❑ The chapter touches upon some general subjects like object-oriented analysis and design.You will explore a few analysis methods like use cases and textual analysis and see howthese techniques can help you define the requirements your software must meet.

One caveat: These are all very important concepts on their own, and this chapter will only tellyou what you need to understand for our discussion of refactoring. So don’t take this chapter as acomprehensive guide on the subject.

❑ You will read about naming guidelines, why they are important, and how refactoring canhelp. (And you’ll even revisit a naming convention well known to veteran VB programmers,Hungarian notation.)

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 197

Page 233: Professional Refactoring in Visual Basic

❑ You will see some refactorings that can help you in closing the gap between code and problemdomain, like the Rename and Safe Rename refactorings, which can help you eliminate smellslike Unrevealing Names and Long Method. You’ll even get to hear a few words of VB trivia.

❑ Then I will talk about the difference between published and public interfaces. This difference ispurely conceptual, meaning that you will not be able to use code to enforce it. But it has a greatimpact on the level and kind of refactorings you are allowed to perform on the code.

After this chapter is over, you will be much more aware of good practices that make code readable tohumans. Taking into account that other people will work on your code is not only altruistic, it is also animportant part of what being a good programmer is all about.

Understanding the Problem DomainThere are many approaches to creating software. Some developers are happy to dip into the code rightfrom the project’s outset, while others prefer to have everything laid down, described, and modeled indocuments and diagrams, thoroughly worked out before even the first line of code is written. Whereveryour style happens to be, and most likely it is somewhere in the middle, you without exception need tospend a significant amount of effort on understanding the requirements, the problem domain, and thegeneral context for your software.

Personally, I am not very keen on separating the roles of analyst and a programmer between two nar-rowly specialized individuals, something that is common practice in larger teams adhering to some of themore traditional and formal software-development methodologies. The problem with this approach is thatit introduces another “level of indirection” between the user and the developer. And if another step incommunication is established on the customer’s side, in the form of someone delegated to represent all theinterested parties, like final users and domain experts, a lot of disruptive noise is introduced during therequirements-gathering process.

You will generally start out by trying to get informed on the problem domain and trying to understandclient needs. These initial activities on the project can take the form of meetings, interviews, correspondence,and/or document exchange. Your interlocutor can be any interested party — the final user, manager,domain expert, or someone else. This process will typically go through certain phases of knowledge acquisi-tion and structuring that I describe in the next section. At the first step you will probably be confronted witha wealth of information that you understand only partially, and your reaction will be to try to understandand organize this information. Bear in mind that the steps I am about to describe are performed iterativelywith an evolutionary approach in mind. So take a look at this typical cycle of software analysis.

Step One: Gathering the InformationWhen you get started, unless you have previously made a system that caters to the same businessdomain, you’ll inevitably end up with that “first day at work” feeling. A lot of jargon will be used atthe first meetings, making it difficult for you to understand the complete meaning of the conversation.At this point it is important to gather as much information as possible, to start to organize it, and tocarefully take note of all the doubts you might have and all the facts you need to further clarify orhave explained to you.

Definition: A problem domain is a term that refers to all real-world concepts, entities, andprocesses that are part of the problem to be resolved by the system you are constructing.

198

Part III: Getting Started with Standard Refactoring Transformations

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 198

Page 234: Professional Refactoring in Visual Basic

In order for this process to be effective, you should hear the opinions of as many interested parties aspossible. You might wish to speak to managers, to final users, domain experts, contractors, and so on.They all almost certainly have their own unique views of the project, the business, the requirements, andthe priorities of those requirements. It’s your task to serve as a sort of mediator, reconciling differentviews and making sure that all legitimate concerns are met.

On one project for a major bank that I participated in, an IT head manager was very keen on exploiting thebenefits of browser-based thin-client architecture: lower administration costs, ease of distribution, simplehardware upgrade path, and so on. A part of the application was to cater to cashiers, a typical teller frontend. They were still using a standard application made in Visual Basic 6, which had a DOS-like character-based interface. After spending some time with them, I realized that they needed an extremely responsiveuser interface that would operate without a glitch or a pause and in which all the options were easily acces-sible through different keyboard shortcuts. I decided to make a very simple browser-based prototype thatcashiers could try out. It was not long before the solution shortcomings in these particular circumstancesbecame apparent. The system’s lack of responsiveness, and the inability to use standard key combinations(the mouse was used for those instead), were described as “torture” by one of the employees. It was soonapparent that in this case that particular technology was not applicable, and the IT department wasswayed towards a compromise solution: a browser-distributed standalone .NET managed executable.

Still, unless you are able to communicate well, you have no use for the information that you havegathered during this first step. So the next step involves developing a common vocabulary.

Step Two: Agreeing on the VocabularyAfter the initiation, you need to make sure that all the information you have gathered is as precise aspossible. In order to do that, you first must make sure that everybody participating in the project speaksthe same language. As you can imagine, I am not referring to English, German, or some other naturallanguage. In every line of business, a specific language is developed with precise meanings that are notimmediately apparent to laymen (or developers in this case). This language can even have different flavors in different departments in the same organization.

It is crucial for enabling communication that common and precise meanings of terms are agreed upon in the early phases of the project. While this process sometimes occurs naturally, it is best if it is tackleddirectly and systematically and if all participants agree on consistent and precise meanings. As the projectprogresses, the vocabulary can be expanded and new terms can be added or even invented.

While developers need to carry the brunt of the process of vocabulary acquisition, other participantsmight need to cope with understanding some technical terms. For example, when you’re discussingusability with final users, those users should have a clear understanding of terms like shortcut key andaccess key and how they differ. These terms can also become part of the common vocabulary.

Something like a vocabulary document can prove to be quite useful for the purpose of formalizing and dis-seminating the common vocabulary. This document can also make the process of integration a lot easierfor newcomers.

The interviews recounted in Chapter 4 showed that the Rent-a-Wheels employees I spoke with haddeveloped their own vocabulary. While the conversations described there were probably easy to under-stand, there are some subtle differences between the meanings of the words as used by the employeesand the common meanings of those words. For example, the word renting for Rent-a-Wheels employees

199

Chapter 8: From Problem Domain to Code: Closing the Gap

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 199

Page 235: Professional Refactoring in Visual Basic

describes the process of vehicle selection and the issuance of the receipt. A vehicle is given to a customeronly after it is handed over. The branch is an internal organizational unit that is defined not by the locationof the reception desk, but by the presence of its own parking lot.

Here is a small section of the vocabulary document developed for the purpose of the Rent-a-Wheelsapplication.

After you have analyzed the business and agreed on the vocabulary, it’s time to think about the require-ments that the system you are constructing should fulfill. Requirements are best described in the form ofusage scenarios, or use cases. You see more about that in the next step.

Step Three: Describing the InteractionsNow that you have an understanding of the problem domain and the purpose of the software you arecreating, it is a time to portray the requirements in a more structured way. You can do this by describingthe series of interactions between the user and the system that results in something useful for the user.Two of the most common ways to manage requirements are through use cases and user stories.

Use CasesA standard way to describe interactions between the system and the users is by means of use cases. Ause case is a description of a possible dialogue between the user and the system that results in the goaldesired by the user. While use cases may differ in format and level of detail, they focus on the systembehavior as perceived from the outside; they do not involve any assumptions about the way the sys-tem will be implemented.

Chapter 4 contains descriptions of the most important use cases for the Rent-a-Wheels application.

User StoriesA less formal way to manage requirements involves user stories. User stories are generally more condensedand less structured than use cases, and are written by customers in their own language. They do not con-vey enough information to implement the functionality required of the application, so if you use them youshould also have an expert involved in the project. This form of managing requirements is popular with theagile software development community.

Rent-a-Wheels VocabularyVehicle — Any vehicle, like a car, motorcycle, SUV, truck, et cetera, that is in a parkinglot and that a customer can rent. Each vehicle belongs to a single branch.

Renting a vehicle — A process whereby a customer selects a vehicle at the receptiondesk and the receptionist issues the receipt that is used to pick up the vehicle at theparking lot.

Handing over a vehicle — A process whereby the parking lot attendant hands over therented vehicle to a customer.

Branch — A Rent-a-Wheels rental company office that has its own parking lot. This isan internal organizational unit that can have multiple reception desks but always has asingle parking lot.

200

Part III: Getting Started with Standard Refactoring Transformations

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 200

Page 236: Professional Refactoring in Visual Basic

Once you have structured the requirements, you can test your understanding by providing the clientwith the prototype of the software you are about to build.

Step Four: Building the PrototypeWith use cases at hand, you are ready to start the implementation. While prototypes can be throwaway, the best value is obtained by those that are evolutionary. They validate your understanding of the require-ments with the customers, both the functional requirements and ones that are not functional. There are anumber of other uses that prototypes can be employed for, such as testing some design and architecturaldecisions, providing the customer with proof of progress, checking interface design details, and so on.

In previous chapters of this book, you started to witness how the initial Rent-a-Wheels prototype isbeing transformed from the initial prototype into a fully functional application.

Once you start to code, the first thing you need to decide is the names you are going to use in your code.You need to name classes, methods, variables, namespaces, and so on in such way that they are bothunderstandable and consistent.

Naming GuidelinesIn order to write maintainable and readable code, you need to choose the names you employ very care-fully. Good naming is probably one of hardest skills you need to learn as a programmer. The names youchoose need to be concise and consistent and they need to communicate well with your fellow program-mers reading the code. This on the surface is a rather simple issue, but it can prove to be really daunting,even for the experienced programmer. It is important that you remember that perfection need not bereached in the first attempt, and that as you proceed you can simply rename the identifiers you need toimprove. Later on in the chapter you are going to see how this refactoring is performed. You can counton tools to help you with this task, so it is not too time-consuming.

Continued

Smell: Unrevealing NamesDetecting the SmellLook for identifiers that have no clear or immediately obvious meaning. This isapparent when you need to look inside the code in order to know what a certainelement’s purpose is. For example, if you need to look inside the method’s bodyin order to understand what the method is doing, it means that method’s nameis not conveying its intent and should be changed. Alternatively, comments maybe explaining a certain code element’s purpose better than its name, so that youhave to rely on them when reading the code.

Words that are not part of the project’s common vocabulary, do not relate well to theproblem domain, do not belong to the vocabulary of software development, and/or fitpoorly into the project’s general context are also poor choices.

201

Chapter 8: From Problem Domain to Code: Closing the Gap

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 201

Page 237: Professional Refactoring in Visual Basic

So, because this is a rather tricky issue, it might be best to start by exploring some of the guidelines thatare easiest to follow, like capitalization styles.

Capitalization StylesWhile Visual Basic is a case-insensitive language, consistent capitalization style still has an effect on the read-ability and aesthetics of your code. Fortunately, Visual Studio enforces a single capitalization version for oneidentifier, so it will correct any difference in capitalization based on the first occurrence of the identifier. Forexample, if you try to write a variable as itemcount after you have declared it as Dim itemCount AsInteger, Visual Studio will change it automatically to itemCount. Another thing to bear in mind is thatother .NET languages are case sensitive, so if you change the capitalization of some public element in areusable .NET library written in VB, you break the compatibility between your VB library and client codewritten in some other language.

Visual Basic should respect capitalization style in general use in .NET framework libraries. Three stylesare in use, and each is applicable to a certain type of identifier.

❑ Camel case (also known as camelCase) is a capitalization style wherein the first word in theidentifier is written in lowercase and each subsequent word in compound identifiers has itsfirst letter capitalized. Words are joined without the use of spaces, underscores, or any otherspecial characters. For example, corporateAccount.

❑ Pascal case is a capitalization style wherein the first letter in the identifier and in each subse-quent word in compound identifiers are capitalized. Words are joined without the use of spaces,underscores, or any other special characters. For example, MonetaryTransaction.

❑ Upper case is a capitalization style wherein all letters in the identifier are capitalized. Forexample: System.IO or System.Web.UI. This capitalization style is less common and isused for two-letter acronyms.

Table 8-1 shows how these capitalization styles are used correctly in Visual Basic.

Other, more obvious signs of less-than-perfect names are abbreviations, unfamiliaracronyms, or capitalization styles that are not in line with the general .NET frameworkor VB capitalization guidelines.

Related RefactoringUse Rename or Symbolic Rename refactoring to eliminate this smell.

RationaleProgrammers use the names of methods, properties, classes, variables, parameters,namespaces, and so on in order to discern the purpose of some piece of source code.When these names are badly chosen, code fails in communicating its purpose to a programmer. Code that does not communicate is the essence of code that is difficult to maintain.

202

Part III: Getting Started with Standard Refactoring Transformations

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 202

Page 238: Professional Refactoring in Visual Basic

Table 8-1: Identifiers and Capitalization Styles in Visual Basic

Simple Naming Guidelines Here are some fairly simple naming guidelines that are part of the .NET framework naming conventionsand should be followed by all .NET languages. They consist of some simple rules that can be enforced with-out a lot of intellectual effort, but should be respected for consistency, readability, and aesthetic purposes.

FxCop, a free analysis tool available for download at http://www.gotdotnet.com/Team/FxCop/,is capable of performing an intelligent spelling check on your code using capitalization to separatenames into constituent English words.

❑ Suffix any exception class with Exception, as in InsufficientFundsException.

❑ Suffix any attribute class with Attribute, as in SerializableAttribute.

❑ Suffix any event handler with EventHandler, as in AlarmEventHandler.

❑ Suffix any event arguments class with EventArgs, as in UnhandledExceptionEventArgs.

❑ Prefix any interface identifier with I, as in IDisposable or ISerializable.

❑ Avoid the ALL_UPPER_UNDERSCORE_DELIMITED style of declaring constants.

❑ Avoid misspelling words. As a matter of fact, you should be able to check your code with aspell-checking tool like Word after compound names are broken apart.

Identifier Capitalization Style

Class Pascal case

Method Pascal case

Property Pascal case

Parameter Camel case

Protected or private instance field Camel case

Local variable Camel case

Shared or public instance field Pascal case

Namespace Pascal case

Enum Pascal case

Interface Pascal case (starting with a capital I, as inIDisposable)

Event Pascal case

203

Chapter 8: From Problem Domain to Code: Closing the Gap

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 203

Page 239: Professional Refactoring in Visual Basic

❑ Avoid using abbreviations and acronyms, unless they are widely used and understood. So naming a method ToXmlNode() is fine — no need to use the excessively longToExtensibleMarkupLanguageNode(). However, don’t use CalcFedTx() or anything of that sort; use CalculateFederalTax() instead.

❑ Finally, avoid Hungarian notation (see sidebar). Since this technology is made obsolete by moderntools, stick with easily pronounced and easily memorized names and rely on Visual Studio to pro-vide type safety and type information.

Good Communication: Choosing the Right WordsWhen choosing names, you have to always bear in mind one thing: the code you are writing is to be readby other people. It has to be easy to understand, informative, simple, and consistent, and it should followthe rule of least surprise. This way another person will easily be able to change the code, to understand it,and to become part of the team. And unless you have infinite memory, you will find that good naming

Hungarian NotationHungarian notation was invented by Charles Simonyi, a distinguished programmer of Hungarian origin at Microsoft. In Hungarian notation a variable name is used toindicate variable type, intended use, or even scope. The variable name is composed of a prefix containing additional type information and a variable name as such. Forexample: strName, meaning a string variable containing the name information.

An elaborate list of abbreviations had to be developed and documented for this pur-pose. These abbreviations were then combined to make identifier prefixes for names.So, for example, piszMessage is assembled from p-i-sz-Message and means a (p)pointer to an (i) index into an array of (sz) null-terminated strings (Message) contain-ing message information. In pre-.NET versions of Visual Basic, Hungarian notationwas a part of Microsoft’s official programming guidelines and was considered a goodpractice by the general VB programming community. You typically saw a lot of vari-ables named things like lblName or btnSave, where the lbl prefix stood for “label”and btn for “button.”

When Hungarian notation was invented, programmers had to worry about typesafety because operations performed on incompatible types were a major source ofbugs. (I talked about type safety in VB .NET in detail in Chapter 5.) Type informationcontained in the variable name helped programmers with type safety.

However, this approach also had a number of drawbacks. The list of abbreviations soongrew out of proportion, and the abbreviations were often difficult to memorize and pro-nounce. Often the logical letter combination was already taken, so some other combina-tion, poorly associated with the term it represented, had to be used. Furthermore, if theunderlying type changed, unless the name changed also, the type information conveyedwould be false.

Today, Hungarian notation is widely considered obsolete. Compilers check typecompatibility and ensure type safety, and IDEs with IntelliSense and other VisualStudio capabilities are much more powerful in conveying type information and providing type safety.

204

Part III: Getting Started with Standard Refactoring Transformations

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 204

Page 240: Professional Refactoring in Visual Basic

practices pay off even with code that you wrote yourself. This section discusses some guidelines for making your source code informative.

Using the Problem Domain Vocabulary as a Source for Identifier NamesFirst of all, try to use the words from the problem domain that are already part of the common vocabularyon the project as a basis for naming the elements in your code. This way, a more direct link between thesource code and the requirements is established, and the purpose of certain elements in your code is easilyunderstood.

If you go back to Chapter 2 and the Calories Calculator sample application, you can see that words likePatient and compound words like WeightInPounds and DailyCaloriesRecommended are used.These are all terms that are easily related to the specific problem this application addresses: recommend-ing a daily caloric intake.

As your code evolves, more complex design is introduced and terms from the problem domain might notsuffice to explain your intent. However, there is one more domain that is easily understood by anyone whoneeds to read or change the source, and this is the solution domain.

Using the Solution Domain Vocabulary as a Source for Identifier NamesA typical programmer understands typical programming terms and paradigms from computer science andsoftware development. Therefore algorithms, patterns, mathematical terms, and common programmingidioms won’t raise any eyebrows when used in your code. There is no more than a slim chance that anyonebut professional programmers will ever look inside the code. Even so, it is quite possible that you will have more than one layer of design in your code, and internal implementation details can often be easilyexplained in solution domain terms.

Choosing One Word to Represent One Meaning and Sticking with ItAnother important characteristic of your source code is that names are applied consistently. While a richvocabulary can enhance the creations of a poet, in software you have different values. A single name shouldbe employed to indicate a single concept. Even if the same name can serve well for some other concept,choose another word, because any ambiguity in your code adversely affects its ability to communicate.When applying this guideline, you have to be much stricter than any natural language.

Choosing Words That You Can Easily Pronounce and MemorizeYou will often discuss your code with your teammates, so make life easy for everyone and choose namesthat are easy to pronounce. This guideline is related to one about avoiding Hungarian notation, with its theabbreviations and acronyms: those are naturally more difficult to pronounce. On the other hand, a problemwith excessively long names is that they are more difficult to memorize. You no longer have the problemsthat older programmers had when different environments limited the number of characters they could useto name a variable, or when long names had a visible impact on memory consumption. All the same, try toavoid excessively long names for simplicity’s sake.

Using Nouns and Verbs ConsistentlyAs a general rule, use nouns for object and class names, and use verbs to name methods. You can usenouns or adjectives to name properties.

205

Chapter 8: From Problem Domain to Code: Closing the Gap

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 205

Page 241: Professional Refactoring in Visual Basic

Rename RefactoringIn a perfect world you would get these things right the first time. However, you often won’t choose thebest names for your code the first time around. Once you come up with a better name, there is no reasonnot to change it.

Since the advent of refactoring tools, the once-tedious task of searching for all the invocations of codeand replacing the old name with the new name has been reduced to only a few clicks of the mouse.

Refactoring: RenameMotivationPoorly chosen names prevent the code from communicating with the programmer.They make it difficult to understand the purpose of the code and to establish the linkbetween the problem domain and the code. This makes maintenance work a lot moredifficult, because it is hard to find the spot in the code that needs to be modified. If thepurpose of the existing code isn’t clear, poorly chosen names can even result in codeduplication and poor reuse. In order to avoid all this, we can use Rename refactoring.

Related SmellsUse this refactoring to eliminate the Unrevealing Names smell or to rename elements that do not comply with general naming guidelines.

MechanicsChange the name of the element that suffers from the Unrevealing Name smell. Thensearch for all the code that invokes the recently changed element and rename it as well.(When you use tools this step is automated.)

BeforePublic Class FemalePatient

Inherits Patient

‘ ...‘The name DailyCalRec uses abbreviations and does not‘communicate very wellPublic Overrides Function DailyCalRec() As Decimal

Return 655 + (4.3 * WeightInPounds) + _(4.7 * HeightInInches) - (4.7 * Age)

End FunctionEnd CLass

Imports NUnit.Framework

<TestFixture()> Public Class TestFemalePatient

‘ ...<Test()> Public Sub DailyCaloriesRecommended()

Dim femalePatient As FemalePatient _= New FemalePatient

206

Part III: Getting Started with Standard Refactoring Transformations

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 206

Page 242: Professional Refactoring in Visual Basic

femalePatient.Height = 72femalePatient.Weight = 110femalePatient.Age = 30

Dim expectedResult As Decimal = 1015.2

Dim realResult As DecimalrealResult = _‘The method DailyCalRec is invoked herefemalePatient.DailyCalRec()

Assert.AreEqual(expectedResult, realResult)End Sub

End Class

AfterPublic Class FemalePatient

Inherits Patient

‘ ...‘The long form is much easier to understandPublic Overrides Function DailyCaloriesRecommended() As

DecimalReturn 655 + (4.3 * WeightInPounds) + _(4.7 * HeightInInches) - (4.7 * Age)

End FunctionEnd CLass

Imports NUnit.Framework

<TestFixture()> Public Class TestFemalePatient

‘ ...<Test()> Public Sub DailyCaloriesRecommended()

Dim femalePatient As FemalePatient _= New FemalePatientfemalePatient.Height = 72femalePatient.Weight = 110femalePatient.Age = 30

Dim expectedResult As Decimal = 1015.2

Dim realResult As DecimalrealResult = _‘Invocation code also has to be changedfemalePatient.DailyCaloriesRecommended()

Assert.AreEqual(expectedResult, realResult)End Sub

End Class

207

Chapter 8: From Problem Domain to Code: Closing the Gap

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 207

Page 243: Professional Refactoring in Visual Basic

Rename Refactoring in Refactor!Rename refactoring in Refactor! is applicable to private fields, properties and methods, and local variables.Once you select your target, it turns all references to a variable to linked identifiers and they are all renamedsimultaneously with the target. Figure 8-1 shows how all occurrences of the field firstNameValue are high-lighted when the cursor is placed over the field and the Rename option in the Refactor! menu is invoked. Allreferences to the field are renamed simultaneously as you start to type.

Figure 8-1

For public elements, Refactor! provides Safe Rename refactoring, which I talk about in the next section.

Published and Public InterfacesOne of the reasons for the great popularity of Visual Basic is the component paradigm. Components arereusable binaries with well defined interfaces. Visual Basic programmers quickly learned how to makeuse of the ready-made COM components that provided an enormous productivity boost. The compo-nents provided new opportunities for reuse and encapsulation that led programming in VB to a newlevel of Rapid Application Development (RAD). Some of the great features of COM technology were the ability to create custom components in VB and to develop and use components across languages, so VB programmers could use components developed in C++ and vice versa.

While component technology in VB was a great advancement, programmers had to treat some issueswith care when using or creating components. For example, if the interface of the component waschanged with some new version of the component, the compatibility between the component and theclient would break, and replacing the old version with the new version of the component would resultin an application breaking down.

At the base of any component technology are the interfaces. Here I define the word interface in broadterms as anything exposed by the component to the outside world. Interfaces help you define the con-tract on how you can communicate with a certain component. Visual Basic also supports an interface asa language construct, wherein the contract is explicitly defined and separate from the implementation.While you can change the interface in a self-contained application with minimum consequences, thesame operation with reusable modules can have far-reaching effects.

208

Part III: Getting Started with Standard Refactoring Transformations

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 208

Page 244: Professional Refactoring in Visual Basic

Self-Contained Applications Versus Reusable ModulesWhen programming, you can roughly categorize the project you’re working on into one of two groups,based on the reach your code has.

❑ Self-contained applications are those in which the code is used only in the context of the currentapplication and nowhere else. It also means that you have in reach all the client code of any class orother construct used in the code. In case you need to modify a signature of a method, for example,you are able to find all invocations of the method and to change them accordingly so the code doesnot break.

❑ Reusable assemblies are components and libraries that were created to be used by others inmany different projects. In this case you generally do not have any idea of the client code that isusing your component or library; neither is it possible for you to modify it. When releasing newversions of these components or libraries, you have to act with utmost care in order not to breakcompatibility of existing applications. When breaking compatibility with previous versions can-not be avoided, because in the end all projects have to evolve, a clear upgrade path and version-ing policy must be provided.

You can observe the difference between the two categories by comparing the direction of dependenciesbetween assemblies depicted in Figure 8-2.

Figure 8-2

When you deal with any private element, like a field or a method, you can be sure that any changes youperform are not far-reaching. You can easily find any invocation or reference to a private method or fieldand fix the problem that this change has caused.

Even when changing a public element in a self-contained application, you can be confident that in theend you’ll be able to fix any problem caused by the change. However, when changing public elements in reusable modules you have no idea of the cascading effect of change you might provoke. This meansyou have to develop a good versioning plan and a clear versioning policy for your product. If you donot, you can place clients in a very frustrating position.

Definition: A published interface consists of all public elements from your reusableassembly whose changes can provoke effects you are not able to control, becauseyou do not have access to the client code making use of the assembly.

Third-Party Data Library Other Third-Party Assembly

Client One Client N

Third-Party Widget

Self-Contained Application

Reusable Assembly

Client Two

209

Chapter 8: From Problem Domain to Code: Closing the Gap

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 209

Page 245: Professional Refactoring in Visual Basic

Imagine you are shipping a new version that has fixed a few bugs, but also broken the backward compati-bility of your reusable component, in order to add some new functionality. A few clients will make use ofthe new functionality offered in a new version and will modify their code, and rebuild their projects inorder to do so. So far, so good. On the other hand, probably all the clients will try to make use of the newversion in order to resolve the bugs you have fixed and will do so without rebuilding the project, butrather by simply reconfiguring the application.

Check out the “Redirecting Assembly Versions” article in MSDN to see how applications can be recon-figured to make use of newer versions of assemblies. You can find the article here: http://msdn2.microsoft.com/en-us/library/7wd6ex19.aspx.

If the clients try to simply replace the old version with the new one without rebuilding client applica-tions after applying changes in the source code where needed, the upgrade will fail miserably.

It is worth noting that in .NET you can have multiple versions of assemblies registered side by side.When the client application that is making use of these assemblies is built, it is linked to a specific ver-sion. If you then install a new version, the original client application will continue to work with the orig-inal version of the assembly. In pre-.NET versions of VB, based on COM technology, the newer versionof the component replaced the old one. In cases where the compatibility between versions of the compo-nent was broken, a host of problems, also known as “dll hell,” ensued, resulting in broken applications.

Refactoring: Safe RenameMotivationThe basic motivation for this refactoring is the same as that for the simple Renamerefactoring: making code communicate its purpose and respect the naming guidelines.However, in this case you are dealing with an element that belongs to the publishedinterface, and you cannot reach all invocations of this element. You need to perform therenaming in a safe way.

Related SmellsUse this refactoring to eliminate the Unrevealing Names smell or to rename elementsthat do not comply with general naming guidelines.

MechanicsThis refactoring is generally performed on methods on properties. It is performed in afew consecutive steps.

1. Declare the method or property in your class with the same signature asthe targeted method and give it a new, improved name.

2. Cut the code from the original method or property and paste it into anewly created method with an improved name.

3. Make the original method or property delegate the call to the newly cre-ated method.

4. Mark the original method with ObsoleteAttribute and the System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)attribute. ObsoleteAttribute marks the invocation code of the originalmethod with a warning, while EditorBrowsable(EditorBrowsableState.Never) hides the original method from IntelliSense.

210

Part III: Getting Started with Standard Refactoring Transformations

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 210

Page 246: Professional Refactoring in Visual Basic

Continued

BeforePublic Class FemalePatient

Inherits Patient

‘ ...‘The name DailyCalRec uses abbreviations and does not‘communicate very wellPublic Overrides Function DailyCalRec() As Decimal

Return 655 + (4.3 * WeightInPounds) + _(4.7 * HeightInInches) - (4.7 * Age)

End FunctionEnd CLass

Imports NUnit.Framework

<TestFixture()> Public Class TestFemalePatient

‘ ...<Test()> Public Sub DailyCaloriesRecommended()

Dim femalePatient As FemalePatient _= New FemalePatientfemalePatient.Height = 72femalePatient.Weight = 110femalePatient.Age = 30

Dim expectedResult As Decimal = 1015.2

Dim realResult As DecimalrealResult = _‘The method DailyCalRec is invoked herefemalePatient.DailyCalRec()

Assert.AreEqual(expectedResult, realResult)End Sub

End Class

AfterPublic Class FemalePatient

Inherits Patient

‘ ...<ObsoleteAttribute(“This method has been renamed.” + _“Use DailyCaloriesRecommended instead”), _

EditorBrowsable(EditorBrowsableState.Never)> _Public Overrides Function DailyCalRec() As Decimal

Return DailyCaloriesRecommended()End Function

Private Overrides Function DailyCaloriesRecommended() As DecimalReturn 655 + (4.3 * WeightInPounds) + _(4.7 * HeightInInches) - (4.7 * Age)

211

Chapter 8: From Problem Domain to Code: Closing the Gap

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 211

Page 247: Professional Refactoring in Visual Basic

Modifying the Public InterfacesThere are certain tactics you can use to minimize the ripple effect of changes in reusable assemblies.Refactoring enables you to change code freely, but in the case of public interfaces you have to conductyourself in a more conservative manner. This means that public interfaces should be well thought out,built to last, and as extensible as possible.

Keeping Interfaces Stable and LeanIn this case it is necessary to do a little bit of planning and to try to think how the module might look in the future. This is a tricky thing to do, but making reusable components is not easy. Also, bear in mindthe Open-Closed principle: you should keep your assemblies open for extension and closed for change.

Publishing as Little and as Late as PossibleIn a way the open-closed principle is an extension of the principle discussed in Chapter 7: you shouldkeep the scope and access level of different elements to a bare minimum. Because every developmentprocess is susceptible to changes in requirements and design, postponing the publishing date as long as possible increases your probability of publishing a more mature and stable version of your software.Finally, avoid publishing inside the team that has access to the same code base, because it will slowdown your development and make it less agile.

Implementing Versioning Policies and Adhering to ThemIn .NET, you should use assembly version information to communicate version data. The important part of assembly version information is an assembly version number. This is the number used by .NET

End FunctionEnd CLass

Imports NUnit.Framework

<TestFixture()> Public Class TestFemalePatient

‘ ...<Test()> Public Sub DailyCaloriesRecommended()

Dim femalePatient As FemalePatient _= New FemalePatientfemalePatient.Height = 72femalePatient.Weight = 110femalePatient.Age = 30

Dim expectedResult As Decimal = 1015.2

Dim realResult As DecimalrealResult = _‘Compiler will mark DailyCalRec as obsoletefemalePatient.DailyCalRec()

Assert.AreEqual(expectedResult, realResult)End Sub

End Class

212

Part III: Getting Started with Standard Refactoring Transformations

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 212

Page 248: Professional Refactoring in Visual Basic

runtime to bind assemblies together. Once the assembly is built referencing one specific version ofanother assembly, it uses this same version of the collaborating assembly in runtime. In .NET multipleversions of an assembly can exist in parallel (something not possible with COM dlls). If necessary,assemblies can be redirected to use other versions of collaborating assemblies by means of configurationfiles, machine configuration files, or publisher policy files.

The format of an assembly version number is as follows:

[major version].[minor version].[build number].[revision]

Your versioning policy should convey information about the amount of changes that each version brings.The client should always be able to conclude from version information whether the new version is com-patible with the previous one. Here is a sample policy that you might follow.

❑ Different build numbers — Compatible; no changes to published interfaces allowed

❑ Different minor versions — Backward compatible; additive; nonbreaking changes to publishedinterfaces and making elements obsolete allowed

❑ Different major versions — No compatibility guaranteed

Look at Table 8-2 to see some examples of compatibility between versions. “No” in the “Is Compatible”column means that the two versions are not guaranteed to be compatible; they may be compatible bycoincidence.

Table 8-2: Compatibility Between Different Versions of an Assembly

Using “Safe” Refactorings When Performing ChangesWhen you perform safe refactorings, the new version of the code is kept compatible with the previousversion, but the modified element is declared obsolete. All the work is delegated to a new element. Thismeans that all client code continues to operate as before, but all users are warned that certain elementsare to be discarded in the near future. For the mechanics of safe refactorings, take a look at the SafeRename refactoring discussion earlier in the chapter.

Original Version New Version Is Compatible

1.5.1254.0 1.5.2311.0 Yes

1.5.1254.0 1.5.0000.0 Yes

1.8.1114.0 1.9.1114.0 Yes

1.8.1114.0 1.7.1114.0 No

2.1.2325.0 3.0.1.2990.0 No

2.1.2325.0 1.5.8856.0 No

213

Chapter 8: From Problem Domain to Code: Closing the Gap

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 213

Page 249: Professional Refactoring in Visual Basic

Announcing Your ChangesBy using ObsoleteAttribute, you can communicate to your users the decision to eliminate certainelements in the near future. If you do this in timely manner, you give them the opportunity to preparefor modifications and avoid the surprise and confusion produced by abrupt changes to publishedinterfaces. Never use exceptions for this purpose. Exceptions artificially maintain versions as compati-ble, but applications break unexpectedly at runtime. You can recognize this incorrect use of exceptionswhen you see a method that has only a single statement, one that is throwing an exception under anycircumstances in which that method might be called.

Thinking About Extensibility When you are making reusable components, because refactoring cannot be performed as freely as usualyou should give more thought to design than usual. The following sections discuss three examples oftypical choices you can make when designing your components and how they affect extensibility.

Fields Versus PropertiesIf you use public fields instead of properties, you cannot make your fields read-only if one day it’s neces-sary. Neither can you change the underlying representation of the data or add data-validation routines.You have to change a field to a property, and that is a change that breaks compatibility between versionsof published interfaces. This said, remember that the best option is to keep your field completely private.Not all the data an object needs has to be visible from the outside.

Interface Versus Abstract ClassThe main difference between the interface and the abstract class in Visual Basic is that the abstract (MustInherit) class can also hold implementation for a method that is not marked as abstract (MustOverride). This leads to an interesting afterthought: if you add a new method to an abstract class and provide it with the default implementation, you keep the code compatible.However, if you add a method to an interface, all classes implementing this interface have to bechanged, because the newly added method has to be implemented somewhere along the hierarchy.

Shadowing Versus OverridingRemember, in order to achieve polymorphic behavior you must override elements in the base class. Foryou to be able to do that, those elements have to be declared Overridable in the first place. Consequently,if you are not sure whether you or someone else might need to override some method or a property, youhad better declare it Overridable just in case, unless you have compelling reasons not to do so.

If you are not sure about the difference between shadowing and overriding, take a look at the article“Differences Between Shadowing and Overriding” on MSDN: http://msdn2.microsoft.com/en-us/library/ms172785(VS.80.aspx.

Safe Rename Refactoring in Refactor!After you perform Safe Rename refactoring a new public method or property is created, marked withlinked identifiers so you can write the new name for it. The body of the original method or propertycontains a single line of code, a delegation code to a new method or property. The original is alsomarked with the Obsolete and EditorBrowsable(EditorBrowsableState.Never) attributes.Your applying EditorBrowsable means that the original method or property will not show up inIntelliSense, minimizing its use in the future. The Obsolete attribute produces a warning message

214

Part III: Getting Started with Standard Refactoring Transformations

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 214

Page 250: Professional Refactoring in Visual Basic

in the IDE in code that already uses this method or property, announcing to the user that it is depre-cated and is to be removed in future releases of the assembly. You can see the result of Refactor!’s SafeRename refactoring in Figure 8-3.

Refactor! makes it easy to perform Safe Rename. However, unless you are dealing with published inter-faces, this refactoring is overkill. Refactor! does not provide simple (symbolic) Rename for public ele-ments yet, but it is only logical to assume it will do so in the future.

Figure 8-3

Continued

An Object-Oriented Design Principle: Open-Closed You have already seen me mention “change” in this book quite a few times. It’s a preva-lent force in software development, and there is no use fighting it. New and changingrequirements are the order of the day for any typical software development shop. Theproblem with changes comes when they start producing an uncontrolled ripple effectinside your code, wreaking havoc and unexpectedly breaking code or introducing bugs.

If you are able to write code in such a way that new features can be written as addi-tions that do not require you to compile the original assembly again, you can be surethat no change has been introduced into the assembly.

DefinitionAccording to Bertrand Meyer, in Object-Oriented Software Construction (Prentice Hall,1988), “Software entities (classes, modules, functions, etc.) should be open for exten-sion, but closed for modification.”

I want to examine this definition.

First, I want you to understand what is meant by “closed for modification.” When youcreate a piece of software, you need to go through the significant effort of analyzing theproblem, gathering the requirements, designing the software, implementing the solu-tion, and testing the result. Each time you introduce a change to your code, you have togo again through more or less the same cycle. If not, you risk shipping software that isfraught with bugs. So once you ship your assembly, you should try not to modify itunless you need to fix a bug.

215

Chapter 8: From Problem Domain to Code: Closing the Gap

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 215

Page 251: Professional Refactoring in Visual Basic

However, you need to be able to respond to new-feature requests. The solution is to beable to extend the behavior of your assembly. This is why entities should be “open forextension.”

The way to achieve this in object-oriented software is through the abstraction. The“client” class should always depend on the abstraction and not on the concrete imple-mentation. This way you can modify the client and add new functionality by creatingnew implementations of the abstraction. In Visual Basic you generally use interfacesand abstract classes to express these abstractions. Take a look at an example to see howthis works out in more concrete terms.

ExampleThink back to Chapter 2 once more. If you remember, at one point I had my Patient classwith a host of *Man and *Woman methods. So, if I wanted to calculate the recommendeddaily amount of calories for a male patient, I’d call the DailyCaloriesRecommendedManmethod. For a female patient, I’d call DailyCaloriesRecommendedWoman. I also createdthe PatientHistoryXMLStorage class to take care of the persistence functionality of theapplication. I generated XML to save patient data. Had I kept the *Man and *Womanmethods I would have to program an XML generation routine like this:

‘ ...“<dailyCaloriesRecommended>” + _IIf(patient.Gender = Gender.Male, _patient.DailyCaloriesRecommendedMan.ToString, _patient.DailyCaloriesRecommendedWoman.ToString + _“</dailyCaloriesRecommended>” + _‘ ...

The problem with this code is that it is not open for extension. Imagine that I wouldlike to add new functionality to my application so it can calculate a recommendeddaily amount of calories for child patients. The only solution would be to modifyPatientHistoryXMLStorage’s XML-generation routine by changing the IIf functionto a longer, If-Else statement:

‘ ...Dim dailyCaloriesRecommended As DecimalIf (patient.Gender = Gender.Male) Then

dailyCaloriesRecommended = _patient.DailyCaloriesRecommendedMan

ElseIf (patient.Gender = Gender.Female) ThendailyCaloriesRecommended = _patient.DailyCaloriesRecommendedWoman

ElseIf (patient.Gender = Gender.Child) ThendailyCaloriesRecommended = _patient.DailyCaloriesRecommendedChild

End If‘ ...“<dailyCaloriesRecommended>” + _ dailyCaloriesRecommended.ToString _“</dailyCaloriesRecommended>” + _‘ ...

216

Part III: Getting Started with Standard Refactoring Transformations

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 216

Page 252: Professional Refactoring in Visual Basic

Rename and Safe Rename Refactoring in the Rent-a-Wheels Application

By just a very superficial visual analysis, you can see a number of obvious flaws in the naming practicesfor the Rent-a-Wheels application code. The mix of abbreviations and Hungarian notation used in thecode means you have to perform extensive code modifications. Table 8-3 shows some of the Renamerefactorings that need to be performed.

Table 8-3: Rename Refactoring and the Rent-a-Wheels Application

Continued

Original Name Renamed To

FrmRt VehicleRental

FrmCat VehicleCategoriesMaintenance

oCn connection

oCmd Command

I have implemented a new functionality, but it is more complicated than necessary anderror-prone. Finally, I had to modify and rebuild the original assembly, exactly the thingI should try to avoid according to the open-closed principle.

Fortunately, I spent some time refactoring my application so that by the end of Chapter 2,the XML-generation routine looked like this:

“<dailyCaloriesRecommended>” + _patientValue.DailyCaloriesRecommended.ToString + _“</dailyCaloriesRecommended>” + _

The patientValue variable references the instance of either MalePatient orFemalePatient. In both cases the code of the XML-generation routine is the same.This is possible because both the MalePatient and FemalePatient classes areextending the common parent class, Patient, and the routine was programmed to anabstraction, not to an implementation. Now say I need to calculate a recommendeddaily amount of calories for child patients. All I need to do is add another class to myproject: ChildPatient. This class, just like MalePatient and FemalePatient, needsto extend the common abstraction, the Patient class:

Public Class ChildPatientInherits Patient

The variable patientValue in this case points to an instance of ChildPatient, and the XML-generation routine is left untouched. This means that the code is open for extension.

217

Chapter 8: From Problem Domain to Code: Closing the Gap

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 217

Page 253: Professional Refactoring in Visual Basic

Table 8-3: Rename Refactoring and the Rent-a-Wheels Application (continued)

Most of this refactoring work goes smoothly; however, a couple of things to have in mind are the following:

❑ When you rename a control or a form in Visual Studio, event-handling routines are not updated.You must rename event-handling routines manually. For example, you must manually changethe name FrmBranch_Load to BranchMaintenance_Load after the form FrmBranch has beenrenamed BranchMaintenance.

❑ Sometimes local variable names can clash with field names. In that case you can resolve the naming conflict by qualifying the field with Me. For example, Me.branches =branches.Tables.Item(0).

Since Rent-a-Wheels is a self-contained executable, there is no need to use Safe Rename refactoring.

SummaryYou have seen in this chapter how there is more to names than you might think at first. They help youestablish the link between the problem domain and the source. In order for this link to be effective, it isimportant to formalize the language you are using in the project through a common vocabulary. Andbecause you will rarely get things just right the first time around, you can use Rename refactoring to correct and improve the choice of words in your code.

You obtain further benefits in code comprehensibility and maintainability by consistent naming prac-tices. For these ends, always follow Visual Basic and .NET naming conventions and guidelines. A muchmore complex issue is the right words to employ when writing code. Try to base your word choice onproblem and solution domains, stick with a single meaning for each word, and use each word to repre-sent a single concept in your code. Also, try to create names that are easy to memorize and pronounce.

When dealing with reusable assemblies, you do not have the same freedom with your code as when youwork on self-contained projects. While you are still free to change the underlying implementation, anychange to the interface used by others can provoke numerous problems for clients. You cannot assume thatclients should rebuild and modify their applications with each release you perform. Instead, you need toclearly identify published interfaces, those that are freely exposed to users of your reusable assemblies. Youmust deal with such interfaces with utmost care, always announcing incoming changes before implement-ing them, respecting versioning policies, performing refactorings safely, and designing the code in such away that it can withstand the changes and is open for extensibility.

In the next chapter you’ll dive right into the world of object orientation, its basic principles, and somevery common refactorings that will let you efficiently combat complex and unstructured code on themethod level.

Original Name Renamed To

oAdapter adapter

txtName BranchName

FrmBranch_Load BranchMaintenance_Load

218

Part III: Getting Started with Standard Refactoring Transformations

79796c08.qxd:WroxPro 2/25/08 9:02 AM Page 218

Page 254: Professional Refactoring in Visual Basic

The Method ExtractionRemedy for Duplicated Code

In this chapter we go back to the basics of object-oriented principles.

❑ You have certainly heard of encapsulation, but the benefits of encapsulation, like those of information and implementation hiding, are not automatically obtained. Nor can youguarantee them by using object-oriented capable language.

❑ Then you will investigate the simplest form of encapsulation: a function. The same principlesthat can be used to improve function organization can be used with methods. You will seewhy long methods are problematic and how they can be improved by method extraction.

❑ After that, you will face the dark side of programming in its worst form: duplicated code.You will see how duplicated code is potentially disastrous and why you should try toavoid it. You will see how even simple refactoring such as Extract Method can do a lot to avoid code duplication. Sometimes duplication is so misused it turns into a real anti-pattern, or so-called copy-paste style. In this chapter you will start to learn how to reusecode much more effectively.

❑ Finally, you are going to take a look at one of the oldest ailments from which your codecan suffer: magic literals. Fortunately, you are going to learn about the remedy and howmagic literals can be replaced with constants and enumerations.

Why Keep Code Encapsulated andDetails Hidden?

You often hear that encapsulation is the first pillar of object orientation. However, encapsulation in software did not start with object orientation. It was actually invented a long time before that.

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 219

Page 255: Professional Refactoring in Visual Basic

In the very early days of programming, people noticed that certain patterns of instructions repeatedthemselves many times in code. In those days computer memory was a limited resource, so puttingthese instructions in only one place could result in significant memory savings. Soon they were puttingthose instructions together and calling them by a single name, instead of writing them each time theywere needed. This is how the subroutine was invented.

Very soon, an interesting side effect was noticed; subroutines made the code easier to comprehend andgrasp by programmers. Since a human brain is also limited in memory and in the number of ideas it canprocess, hiding certain details from the direct view had the very positive result of simplifying the codefor the programmer. A program could be composed of smaller units, thus making it easier to tackle com-plex problems, which could also be decomposed. This was much more convenient than using controlflow and numerous GOTO statements for the same purpose, which can produce very complex, tangledcode that is difficult to understand and follow, also known as spaghetti code.

Code like this inspired what is probably one of the most famous essays in software literature, “Go ToStatement Considered Harmful,” by Edsger Dijkstra.1

A few other benefits also resulted from use of the subroutine:

❑ Improved reuse resulted in better productivity, because the same solution for a certain problemcould be used over and over. Improved reuse meant less code and less duplication.

❑ Readability of the code was improved, because chunks of code could be given human-readablenames. This enabled programmers to establish a link between the code and the problem domain.(We discussed the benefits of establishing a link between the code and the problem domain in theprevious chapter.)

❑ Because you can reference a piece of code by its name, encapsulation means there’s no need to know any details of its implementation. This effect is generally referred to as information andimplementation hiding and you will learn more about it in the next section.

Information and Implementation HidingNow, if this chunk of code can receive parameters and return values, it means it can be observed from theoutside as a unit that receives input, does some processing, and produces the desired output. When view-ing the code from the outside, you are not concerned about implementation details; all you need to knowabout the function is its interface or the public part. You need not know any implementation details, suchas the operations or local variables that the method is using. Simply put, to use the function you need toknow the declaration, but you don’t need to know anything about the body of the function. For example,if you have a function that will return a sorted array of Account objects:

Public Function SortAccounts(ByVal accounts As Account()) As Account()

1Edsger Dijkstra (March 1968). “Go To Statement Considered Harmful.” Communications of the ACM 11 (3): 147–148.

Definition: Spaghetti code is code that has a complex and tangled control structureresulting from extensive use of GOTOs and other unstructured constructs. Such codeis very difficult to read, understand, debug, modify, test, and maintain.

220

Part III: Getting Started with Standard Refactoring Transformations

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 220

Page 256: Professional Refactoring in Visual Basic

You need not know which sort of algorithm the programmer that implemented the method used. That pro-grammer could have used bubble sort, quicksort, heapsort, or any other algorithm. Thankfully, for you toget your sorted array of the Account object, you do not need to know any of these algorithms, nor evenwhich of them has been used. You don’t have to write this code, or even understand it; you can still use itand profit from it. (As an accomplished programmer you should be familiar with these sorting algorithms,mind you.)

What’s even more, if encapsulated code one day needs to be changed, as long as you do not changeany public parts (also known as the interface), you do not have to change any other parts of yourcode; the change is successfully localized. The possible ripple effect of changes in your code is thuseffectively minimized.

Encapsulation and information and implementation hiding have more sophisticated meanings in object-oriented programming. I will get back to the subjects of encapsulation and information and implementa-tion hiding in Chapter 11, but first take a look at how it all works out on a procedural level.

In order to organize your code in the form of well-structured, well-encapsulated methods that are easyto use, reuse, and understand, you should apply certain heuristics that are described in the next section.

Continued

Refactoring: Extract MethodMotivationSmall, well-named, granular methods will benefit your code in a number of ways:

❑ Better code comprehension: You can easily understand the purpose of thefew instructions in the code without getting bogged down with implemen-tation details.

❑ Increased reuse: There is a greater probability for more granular methodsto be reused.

❑ Minimized code duplication: With more effective reuse, code duplicationis minimized.

❑ Encapsulation of code: You don’t necessarily need to understand how amethod is programmed in order to use it.

❑ Changes can be performed in isolation: As long as the public part of amethod is not changed, the body of the method can be changed without a ripple effect on the rest of the code.

❑ Methods can be unit-tested: If you test large methods, the benefits of unittesting are reduced. You will be able to detect the error but will still have toput in an effort to discover where exactly the error was produced.

❑ Describe code better than comments: Comments can easily become obso-lete. Because the comments do not have any effect during execution, it isvery easy to change the code but forget to change the comments. Suchcomments are misleading and counterproductive.

This is one of the most basic and most common refactorings, and it is performed often. Itis essential for simplifying your code and making it easier to maintain and comprehend.

221

Chapter 9: The Method Extraction Remedy for Duplicated Code

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 221

Page 257: Professional Refactoring in Visual Basic

Related SmellsUse this refactoring to eliminate Long Method, Duplicated Code, and Comments smells.

MechanicsThere are no strict definitions for a long method But as soon as a method is doing morethan one thing, the comments are used to explain what a section of the method is doing, or you can see that certain sections of code repeat themselves in more than one place, it istime to decompose the method by means of method extraction. Depending on local vari-ables, this process can be more or less complicated. In its complete form it can take the following six steps:

1. Identify the section of the method you will extract and think of a name thatexplains the intent well.

2. Declare a new method and copy the section of code you are extracting tothis method.

3. Search for local variables that are used only inside the new method anddeclare them inside the new method.

4. Search for local variables from the original method that are only read in thenew method. These variables should be declared as method parameters.

5. Search for a local variable that has been modified in the extracted section.This value should be returned by the method. If more than one variable ismodified in the extracted section, you will need to simplify the code furtherbefore performing the extraction. (See the Replace Temp with Query andSplit Temporary Variable refactorings in Chapter 10.)

6. Replace the extracted section of code in the original method with the call tothe newly created method. Delete the declaration of the local variable thatyou declared in Step 3 and that is now used only in the new method.

Note: Local variables can make method extraction much more difficult. Passing toomany parameters and returning too many values will result in an unnecessarily com-plicated method wherein the benefits of extraction are lost. In those cases you shouldtry to simplify the code before performing extraction. For example, you can eliminatesome local variables by performing Replace Temp with Query and Split TemporaryVariable refactoring (see Chapter 10).

BeforePublic Function RenderFundsTotalCell() As String

Dim total As DecimalFor Each account As Account In Me.accounts

total += account.BalanceNextDim formattedTotal As StringformattedTotal = “<P>Total:<B>” + _total.ToString(“C”) + “</B></P>”Return “<TD>” + formattedTotal + “</TD>”

End Function

222

Part III: Getting Started with Standard Refactoring Transformations

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 222

Page 258: Professional Refactoring in Visual Basic

Decomposing MethodsAlmost every long method can be decomposed into a number of smaller, more granular methods thatare easier to manipulate and that will make your code more readable. This process of decomposition oflarger method into smaller ones is called method extraction. In order for you to perform this process effec-tively, extracted methods should contain code that fits well together, performing a meaningful singleoperation with clearly defined input and output, and that can be named in such a way that it can easilyestablish the link with the problem domain.

Circumference Calculation — Long Method ExampleThere is no clear-cut formula for performing method decomposition, so it is best if I start off with anexample. I have chosen the following problem: calculating the circumference of circle a given the centercoordinates and the coordinates of an arbitrary point on the circumference. You can visualize this by taking a look at Figure 9-1.

When you look at the drawing, the solution to the problem becomes pretty obvious. It comes down totwo geometrical formulae. This is clear if you decompose your problems into two calculations.

❑ Because you have the coordinates of point C — the circle’s center and point P — a point on the circumference — you can see that the distance between these two points represents a circleradius. If you calculate the radius, then you can use the following formula for calculating circum-ference length: C = 2rπ.

❑ You can see also see on the diagram that the a, b, and r lines form a right triangle, where r is the hypotenuse. This means that r can be calculated as r² = a² + b², according to thePythagorean theorem.

AfterPublic Function RenderFundsTotalCell() As String

Return “<TD>” + FormatTotal() + “</TD>”End Function

Private Function CalculateFundsTotal() As DecimalDim total As DecimalFor Each account As Account In Me.accounts

total += Me.account.BalanceNextReturn total

End Function

Private Function FormatTotal() As StringReturn “<P>Total:<B>” + _Me.CalculateFundsTotal.ToString(“C”) + _“</B></P>”

End Function

223

Chapter 9: The Method Extraction Remedy for Duplicated Code

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 223

Page 259: Professional Refactoring in Visual Basic

Figure 9-1

Because methods are really quite similar to functions — the difference being that a method is associatedwith an object and a function is not (more on that in some of the following chapters) — I have programmedthe first version of the solution using the Sub Main function of the Module construct in a procedural style(Listing 9-1). You will see how you can decompose functions as you start to extract code from Sub Main.Later on in the book, in Chapter 11, you will see the solution programmed in an object-oriented way.

Listing 9-1: Calculate Circumference Length — Unstructured Example

Option Explicit OnOption Strict On

Namespace RefactoringInVb.Chapter9

Structure PointPublic X As DoublePublic Y As Double

End Structure

Module CircleCircumferenceLength

Sub Main()Dim center As PointDim pointOnCircumference As Point‘read center coordinatesConsole.WriteLine(“Enter X coordinate” + _“of circle center”)center.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _“of circle center”)center.Y = CDbl(Console.In.ReadLine())‘read some point on circumference coordinates

y

x

P

C

rb

a

224

Part III: Getting Started with Standard Refactoring Transformations

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 224

Page 260: Professional Refactoring in Visual Basic

Listing 9-1: Calculate Circumference Length — Unstructured Example (continued)

Console.WriteLine(“Enter X coordinate “ + _“of some point on circumference”)pointOnCircumference.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _“of some point on circumference”)pointOnCircumference.Y = CDbl(Console.In.ReadLine())‘calculate and display the length of circumferenceConsole.WriteLine(“The length of circle “ + _“circumference is:”)Dim radius As Doubleradius = ((pointOnCircumference.X - center.X) ^ 2 + _(pointOnCircumference.Y - center.Y) ^ 2) ^ (1 / 2)Dim lengthOfCircumference As DoublelengthOfCircumference = 2 * 3.1415 * radiusConsole.WriteLine(lengthOfCircumference)

Console.Read()End Sub

End Module

End Namespace

You can test this code manually, and you will see that it performs the calculation correctly. However, thiscode is poorly structured, and a single method contains all the code used to solve the problem. You cansee that some comments are marking different sections in the code. If you analyze the code further, youcan see that some code is concerned with communicating with the user for the purposes of obtaining thedata for the calculation and displaying the result, and some other code is performing the actual calcula-tion. This is an important conclusion and can serve as the basis for the function extraction you are goingto perform in the next section.

Smell: Long MethodDetecting the SmellLong methods are easy to detect upon visual inspection. Sheer quantity of lines can bea good starting point. There is no strict rule for when the method is too long, but if it isdoing more than one thing it should be split into several methods. Another certain signthat the method is just too long is comments explaining the purpose of certain sectionsof the method.

Related RefactoringUse Extract Method refactoring to eliminate this smell.

RationaleLong methods are difficult to understand, debug, reuse, maintain, and unit-test. Theseproblems can be remedied by the extraction of sections of code from the original methodinto the new, more granular method.

225

Chapter 9: The Method Extraction Remedy for Duplicated Code

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 225

Page 261: Professional Refactoring in Visual Basic

Extracting Circumference Length Calculation CodeYou will start out with your refactoring of the sample application by moving the code related to mathe-matical calculation into a separate function. Since this code calculates circumference length, you willdeclare a new Sub calculateCircumferenceLength and copy the code that performs the calculationinto the new function. You can even see that the comment on that section approximates well to the nameI have chosen for the new function. You can see the new sub’s code in Listing 9-2.

Listing 9-2: Calculating Circumference Length Function Extraction — Steps 1–3

Private Sub CalculateCircumferenceLength()Dim radius As Doubleradius = ((pointOnCircumference.X - center.X) ^ 2 + _

Smell: CommentsDetecting the SmellThe comments are easily detected by visual examination. When you see that the com-ments are carrying the information that should be communicated by code elementnames, or that this information is superfluous or obsolete, you have detected com-ments that should be refactored out of your code.

Related RefactoringUse Extract Method or Rename refactoring to eliminate this smell.

RationaleThe use of comments is traditionally considered a good programming practice for thesame reasons that you like to refactor your code — comments help you understandwhat the code is doing. Sometimes they can even group sections of code together orhelp you establish a connection between the code and the problem domain. However,comments have one big disadvantage: they don’t get executed, so they are very proneto becoming obsolete.

You can easily change a piece of code and test the results to change the behavior ofyour application, but if you forget to change the comments, no one will notice. No one,that is, until a trusting maintenance programmer tries to understand the code relyingon now-obsolete comments. There is probably nothing as frustrating as being led alongthe wrong path by obsolete comments.

Comments are sometimes used to help explain sections of poorly written code. In thosecases the valid solution lies not in comments but in refactoring the problematic code.

Instead of relying on comments to explain the purpose your code, use information provided by the names of elements in your code to communicate your intention. Thisinformation is always available and is less prone to becoming obsolete.

This does not mean that all forms of comments are bad. For example, XML comments areused to generate documentation and will provide information displayed by IntelliSenseand Object Browser.

226

Part III: Getting Started with Standard Refactoring Transformations

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 226

Page 262: Professional Refactoring in Visual Basic

Listing 9-2: Calculating Circumference Length Function Extraction —Steps 1–3 (continued)

(pointOnCircumference.Y - center.Y) ^ 2) ^ (1 / 2)Dim lengthOfCircumference As DoublelengthOfCircumference = 2 * 3.1415 * radius

End Sub

As soon as you paste the code, two variables, pointOnCircumference and center, are marked bythe IDE as undeclared. This is because these variables are at the beginning of the Main() sub. Thesevariables are read inside the CalculateCircumferenceLength sub, so you need to pass them to theCalculateCircumferenceLength sub. You can do that by declaring these variables as parameters ofthe sub. Now the declaration of CalculateCircumferenceLength looks like this:

Private Sub CalculateCircumferenceLength(ByVal center As Point, _ByVal pointOnCircumference As Point)

With this, the IDE does not report errors any more.

Now the code compiles correctly, but CalculateCircumferenceLength is not really of much use. It calculates the circumference length and stores the value in the lengthOfCircumference variable, butthen does nothing with it. You can see that in the original sub, Main, the value of this variable is displayedto the user, and its calculation is actually the whole purpose of the application. What you need to do is topass this value as a return value of the CalculateCircumferenceLength sub, so that it is again availableto the code in the sub Main. This also means you need to convert the CalculateCircumferenceLengthsub into a function. The CalculateCircumferenceLength function is displayed in Listing 9-3.

Listing 9-3: Calculate Circumference Length Function Extraction — Steps 4 and 5

Private Function CalculateCircumferenceLength(ByVal center As Point, _ByVal pointOnCircumference As Point) As Double

Dim radius As Doubleradius = ((pointOnCircumference.X - center.X) ^ 2 + _(pointOnCircumference.Y - center.Y) ^ 2) ^ (1 / 2)Dim lengthOfCircumference As DoublelengthOfCircumference = 2 * 3.1415 * radiusReturn lengthOfCircumference

End Function

Now that you have successfully extracted the CalculateCircumferenceLength function, all that is left to do is to eliminate the extracted code section from the sub Main and make use of theCalculateCircumferenceLength function instead.

Before that, you should write a unit test for the extracted function, but I will skip this for the sake of space.Once you have transformed the sub Main code so it is calling the CalculateCircumferenceLengthfunction, you can eliminate the comment ‘calculate and display the length of circumferencebecause it is now superfluous and because the name of the new sub is eloquent. You can see what thiscode looks like in Listing 9-4.

227

Chapter 9: The Method Extraction Remedy for Duplicated Code

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 227

Page 263: Professional Refactoring in Visual Basic

Listing 9-4: Complete code after Calculate Circumference Length Function Extraction

Option Explicit OnOption Strict On

Namespace RefactoringInVb.Chapter9

Structure PointPublic X As DoublePublic Y As Double

End Structure

Module CircleCircumferenceLength

Sub Main()Dim center As PointDim pointOnCircumference As Point‘read center coordinatesConsole.WriteLine(“Enter X coordinate “ + _“of circle center”)center.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _“of circle center”)center.Y = CDbl(Console.In.ReadLine())‘read some point on circumference coordinatesConsole.WriteLine(“Enter X coordinate “ + _“of some point on circumference”)pointOnCircumference.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _“of some point on circumference”)pointOnCircumference.Y = CDbl(Console.In.ReadLine())Console.WriteLine(“The length of circle “ + _“circumference is:”)Dim lengthOfCircumference As DoublelengthOfCircumference = CalculateCircumferenceLength(center, _pointOnCircumference)Console.WriteLine(lengthOfCircumference)

Console.Read()End Sub

Public Function CalculateCircumferenceLength(ByVal center As Point, _ByVal pointOnCircumference As Point) As Double

Dim radius As Doubleradius = ((pointOnCircumference.X - center.X) ^ 2 + _(pointOnCircumference.Y - center.Y) ^ 2) ^ (1 / 2)Dim lengthOfCircumference As DoublelengthOfCircumference = 2 * 3.1415 * radiusReturn lengthOfCircumference

End Function

End Module

End Namespace

228

Part III: Getting Started with Standard Refactoring Transformations

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 228

Page 264: Professional Refactoring in Visual Basic

Extracting the Radius Calculation CodeYou have successfully performed your first method extraction. However, you should not stop just yet. Isee that the new function can also be simplified, by the separation of the calculation into the two steps I mentioned when we analyzed the problem initially. The first two lines of the function are concernedwith the radius calculation and therefore can be placed in a separate function. After these functions havebeen extracted you can also take the opportunity to eliminate the temporary (local) variables radiusand lengthOfCircumference because they do not improve code clarity. You can see how the codelooks after calculateRadius function extraction in Listing 9-5.

Listing 9-5: Calculate Radius Function Extraction

Option Explicit On

Public Function CalculateCircumferenceLength(ByVal center As Point, _ByVal pointOnCircumference As Point) As Double

Return 2 * 3.1415 * CalculateRadius(center, pointOnCircumference)End Function

Public Function CalculateRadius(ByVal center As Point, _ByVal pointOnCircumference As Point) As Double

Return ((pointOnCircumference.X - center.X) ^ 2 + _(pointOnCircumference.Y - center.Y) ^ 2) ^ (1 / 2)

End Function

Extracting the “Wait for User to Close” CodeIf you take a look at the end of Sub Main, you will see a somewhat curious line of code: Console.Read().Normally such code is used to read user input, but this code ignores the value the user entered.

You have probably guessed by now, but this line serves only to keep the console window open until theuser decides to close it. If you comment this line, you will see that the application still functions correctly,but the console window closes so rapidly that the user has no chance to read the result.

Someone reading it might be puzzled by this line of code whose purpose is not obvious. To make itspurpose clear, you can extract this single line of code into a separate method. You can name the methodWaitForUserToClose:

Private Sub WaitForUserToClose()Console.Read()

End Sub

So, you can see that even a single line whose purpose is not immediately obvious might be worthy ofmethod extraction, if by doing so you make the code more readable.

Extracting the Read Coordinates CodeNow you should go back to the Sub Main code. This sub is still too long. An interesting observation can bemade with regard to the coordinates’ input code; you can see that the segment for reading the center coor-dinates is quite similar to the code for reading the coordinates for the point on the circumference. The only

229

Chapter 9: The Method Extraction Remedy for Duplicated Code

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 229

Page 265: Professional Refactoring in Visual Basic

difference is in literals containing the message for the user. You can do something about this. You can startby declaring a new sub for reading coordinates and copying the code for reading the center coordinateinto the body of the new sub. You should end up with the code shown in Listing 9-6.

Listing 9-6: Input Point Coordinates Extraction — Steps 1 and 2

Public Sub InputPoint()Console.WriteLine(“Enter X coordinate “ + _“of circle center”)center.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _“of circle center”)center.Y = CDbl(Console.In.ReadLine())

End Sub

Since the code for reading both points is quite similar, you can try to make this method work for theinput of coordinates of both points. That is why I have named the function InputPoint without using a specific point name.

The IDE now marks the variable center as not declared. You can declare it at the beginning of the func-tion and rename it point at the same time. This way you can keep your method point-neutral. Then youshould make the sub return the point so that the original sub can make use of this function. The codefor this version of InputPoint is shown in Listing 9-7.

Listing 9-7: Input Point Coordinates Extraction — Steps 3 and 4

Public Function InputPoint() As PointDim point As PointConsole.WriteLine(“Enter X coordinate “ + _“of circle center”)point.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _“of circle center”)point.Y = CDbl(Console.In.ReadLine())Return point

End Function

You can see that the only thing that keeps this code related to the specific circle center point is the two wordscircle center, part of the literals displayed to a user. You can parameterize this code by adding the newparameter pointName to the InputPoint function. Then you can construct the message to the user by con-catenating a string literal and pointName parameter. Take a look at the new version of the InputPointfunction in Listing 9-8.

Listing 9-8: Input Point Coordinates Extraction — Steps 3 and 4, Part 2

Public Function InputPoint(ByVal pointName As String) As PointDim point As PointConsole.WriteLine(“Enter X coordinate “ + _“of “ + pointName)point.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _

230

Part III: Getting Started with Standard Refactoring Transformations

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 230

Page 266: Professional Refactoring in Visual Basic

Listing 9-8: Input Point Coordinates Extraction — Steps 3 and 4, Part 2 (continued)

“of “ + pointName)point.Y = CDbl(Console.In.ReadLine())Return point

End Function

All that is left to do is to make use of this function in Sub Main. You will eliminate the original coordinate-reading code and replace it with a call to the InputPoint function. You will also eliminate an unnecessarytemporary variable in the process. (I will talk about Inline Temp refactoring in the next chapter.) The finalversion of the code is shown in Listing 9-9.

Listing 9-9: Final Method Extraction Version of the Calculate Circumference Length Function

Option Explicit OnOption Strict On

Namespace RefactoringInVb.Chapter9

Structure PointPublic X As DoublePublic Y As Double

End Structure

Module CircleCircumferenceLength

Sub Main()Dim center As PointDim pointOnCircumference As Pointcenter = InputPoint(“circle center”)pointOnCircumference = InputPoint(“point on circumference”)Console.WriteLine(“The length of circle “ + _“circumference is:”)Console.WriteLine(CalculateCircumferenceLength(center, _pointOnCircumference))WaitForUserToClose()

End Sub

Public Function CalculateCircumferenceLength(ByVal center As Point, _ByVal pointOnCircumference As Point) As Double

Return 2 * 3.1415 * CalculateRadius(center, pointOnCircumference)End Function

Public Function CalculateRadius(ByVal center As Point, _ByVal pointOnCircumference As Point) As Double

Return ((pointOnCircumference.X - center.X) ^ 2 + _(pointOnCircumference.Y - center.Y) ^ 2) ^ (1 / 2)

End Function

Public Function InputPoint(ByVal pointName As String) As PointDim point As Point

Continued

231

Chapter 9: The Method Extraction Remedy for Duplicated Code

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 231

Page 267: Professional Refactoring in Visual Basic

Listing 9-9: Final Method Extraction Version of the Calculate Circumference Length Function (continued)

Console.WriteLine(“Enter X coordinate “ + _“of “ + pointName)point.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _“of “ + pointName)point.Y = CDbl(Console.In.ReadLine())Return point

End Function

Private Sub WaitForUserToClose()Console.Read()

End Sub

End Module

End Namespace

This final version of the sample application looks a lot better. You now have a code that is a lot easier toread and to debug. If you, for example, have an error in calculation, you can easily test two functions incharge of mathematical computation and soon find in which of the two you made a mistake. You have alsomade a method shorter by extracting two very similar sections of code in a single method. This way, youhave eliminated duplicated code from the application. Duplicated code is a probably the worst smell thatyour code can suffer from and is actually so important I decided to dedicate a whole section to it. Before I end this section on method extraction I want to define another important smell very typical of VB code,and the cause of so many poorly written routines. While misuse of event-handling routines can be inter-preted as just another form of the Long Method smell, it is so prevalent and VB programmers fall into thistrap so easily that I decided to define it here as a smell on its own.

Smell: Event-Handling BlindnessDetecting the SmellGreat portions of code in the application are placed inside event-handling routines.

Related RefactoringUse Extract Method refactoring to eliminate this smell.

RationaleWhile this is only a special case of the Long Method smell, it is so common in Visual Basiccode that it deserves special mention. The typical scenario is as follows: you start out yourproject by placing components on the form and filling in the event-handling routineswhose declaration is generated by the IDE when you click the components on the form.As the project progresses, more and more code is simply added to existing event handlers.

The Rapid Application Development (RAD) paradigm grants an important productiv-ity boon, but if it is not coupled with good design and programming practices, all thebenefits are lost over the long term.

232

Part III: Getting Started with Standard Refactoring Transformations

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 232

Page 268: Professional Refactoring in Visual Basic

I want to finish this section on method extraction with something that will make performing itimmensely easier. I want you to see how to perform Extract Method refactoring with Refactor!

Extract Method Refactoring in Refactor!This is one of my favorite refactorings that Refactor! can do. When you are dealing with complex methods,keeping track of all local variables can be complicated and time-consuming. Thanks to Refactor!’s visualaids, you can easily follow which variables are passed to a new method, which are returned by the method’sreturn value, and which are returned as output parameters. This can be a real time-saver. You can seeRefactor!’s Extract Method preview window in Figure 9-2.

Refactor! will correctly pass all variables read in the extracted method as parameters and all variablesassigned in the extracted segment as output parameters. If only one variable in the segment has been assigned, Refactor! will use this variable as the return value of the method. If no variables arebeing assigned in the new method, Refactor! will name this method Sub instead of Function.

Another interesting feature is the treatment that Refactor! gives to comments. When performing thisrefactoring, Refactor! will try to guess the name of the new method based on the comments above theextracted segment. I have already mentioned that comments are a good sign that you need to performmethod extraction, erase the comments, and use the method name to communicate the same informationthat the comments are currently communicating (see the sidebar on the Comments smell earlier in thechapter for more information). This can work out surprisingly well, and often the name that Refactor!comes up with by concatenating the words in comments can be used without alteration.

Figure 9-2

233

Chapter 9: The Method Extraction Remedy for Duplicated Code

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 233

Page 269: Professional Refactoring in Visual Basic

When you are writing methods, strive to write them in such a way that they return only one value. Use ofoutput parameters is discouraged because it is probably a sign that the method is doing more than it should.Another sign that this refactoring has been carefully thought out is the color of the arrow displayed in thepreview window and in the Parameter Count section of the Action Hint window. If a method is returningmore than one value, the color of the arrow is red. If a method is returning a single value by means of thereturn value, the color of the arrow is green for the return value and blue for the input parameters. Thisaspect isn’t shown in Figure 9-3, but you can take my word for it that the arrow is red in the original win-dow, because the number of output parameters is greater than one.

Figure 9-3

Finally, I should mention that before displaying the Extract Method option in the menu, Refactor! willanalyze the code and display this option only if method extraction of the selected section is feasible.

I am sure you will promptly start to appreciate the benefits of Refactor!’s Extract Method. I am also sureyou will start to loathe code duplication after the next section.

The Duplicated Code SmellAt the last step in refactoring the original circumference calculation application, I identified two similarsections of code, extracted and parameterized a function, and replaced the two sections with a call to thenewly extracted function. While the two sections were not completely identical, I managed to recognizethe similarity, parameterize the difference, and effectively eliminate duplication from the code.

However, I still haven’t discussed the problems that duplicated code can lead to. The following listshows the problems duplicated code can create and discusses why it should be eliminated from yourcode base.

❑ Duplicated code adversely affects the maintainability of your code: If you need to modify apiece of code that has been duplicated, you’ll need to search for all occurrences of this code andmodify it more than once. This will obviously make you work much more than necessary, andwith a greater probability of error.

❑ Duplicated code is a powerful source of bugs: Imagine you need to modify your code. Becausekeeping track of code duplication is not very plausible, you are very likely to end up with anumber of occurrences of the same code that you missed and where you failed to implement thechange. Now you have a number of segments that do not behave as expected. This means thatyou have introduced a bug into your code.

❑ Duplicated code makes it difficult to establish a link between your code and the problemdomain: Because the same code is present in more than one place, it is difficult to understandhow your code relates to concepts from the problem domain. This means that the semanticvalue of your code is lost and the general design quality of your code is poor.

234

Part III: Getting Started with Standard Refactoring Transformations

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 234

Page 270: Professional Refactoring in Visual Basic

❑ Duplicated code promotes longer methods: If you are simply copying the code, it means youare not preoccupied with structuring your code. This can often lead to the proliferation of long,non-granular methods. (See the sidebar on the Long Method smell earlier in the chapter tounderstand the adverse effect longer methods can have on your code.)

❑ Duplicated code means a bigger code base: Keeping the number of lines needed in your appli-cation at a minimum simply means that there’s less you need to deal with. In this sense, lesscode generally means less work. You will benefit from a smaller code base when optimizing,unit-testing, maintaining, comprehending, and generally dealing with your code.

Now that you understand why you should avoid code duplication at all costs, you can take a look at themost common sources of code duplication. This way you will be able to recognize duplication-producingbehavior when you program, and prevent code duplication right from the outset.

Sources of Duplicated CodeSeveral sources of duplicated code exist:

❑ The most common source of duplicated code is copy-paste programming. This style of program-ming is so important it deserves its own section, which you will read shortly.

❑ Another source of duplicated code can be simple unawareness that the code already exists inthe project you are working on. Maybe the project is too large, or maybe it is written in such away that it is not easy to understand what the code is doing, so you are not able to find the classthat is performing the action you need to implement.

Smell: Duplicated Code Detecting the SmellSometimes detecting duplicated code can be easy, because you can visually spot almostidentical sections of code in more than one place. This duplication can be the result of asimple copy-paste operation. At other times duplication is not at all obvious, and somelevel of pattern recognition is required. Once the similarity between sections in code hasbeen identified, further analysis is necessary to isolate duplication from the differentiatingcode. For example, a method can be parameterized so it can be used to replace similar sec-tions of code.

Some tools can also perform static analysis of code in search of duplication, but gener-ally this does not go beyond a simple textual comparison of code segments. However,this is still useful for detecting the copy-paste style of duplication.

Related RefactoringA number of refactorings can be employed to eliminate this smell: Extract Method,Extract Class, Extract Superclass, Pull Up Method, Pull Up Property, and so on.

RationaleDuplicated code results in a code base that is more difficult to maintain, because eachmodification has to be performed in more than one place. Such code is also more diffi-cult to comprehend, because it is hard to understand the purpose of the code sections.Code duplication is a sign of poorly designed code.

235

Chapter 9: The Method Extraction Remedy for Duplicated Code

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 235

Page 271: Professional Refactoring in Visual Basic

❑ Sometimes the code can be so badly implemented that you prefer to write new code yourself.

❑ Today, open source has become ubiquitous, and a solution for numerous problems can be foundon the Internet. It is ever so easy to simply copy the code you need into your own project.

❑ Finally, literal values can become a source of duplication, as you will see later on in this chapter.

Whatever the reason, you should try to avoid the duplication. If you are not able to find your wayaround in the project you are working on, it is a good signal that the project code should be refactored.If you implemented some functionality on your own and consciously avoided using some unruly code,you should eliminate that unruly code from the code base altogether and use your improved versioninstead. When using open-source code, try to use a compiled binary: this way you will have easier timeupgrading the new versions of the component.

Copy-Paste ProgrammingThis style of programming is common with novice or inexperienced programmers. It is often the firstmechanism of reuse that a programmer learns and can lead to a significant productivity boost in the initialphase of learning the craft. Sometimes it is even used by more experienced programmers when they arefaced with ever closer deadlines. It is so simple to just copy and paste sections of code that you need.

Code snippets in Visual Studio: the Code Snippets feature in Visual Studio lets you paste predefined or cus-tom code snippets inside your code. Code snippets that come with Visual Studio will help you write somevery common lines of code by letting you paste skeleton code directly into the file you are working on. Thisfeature can improve productivity, especially for some novice programmers. However, if abused, this featurecan be a real hotbed of duplicated code. Beware the trap of copy-paste programming with this feature.

Even if you did duplicate code on purpose, the drawbacks are the same. This style of programmingshould be discouraged, and you should use better, object-oriented methods of reuse like delegation andinheritance. Refactoring techniques are very efficient in promoting reuse and eliminating duplicatedcode, and many refactorings have as a specific purpose the elimination of duplicated code. These tech-niques should be adopted as part of an overall style that promotes better design and reuse.

The next sidebar and section take a look at another dubious programming practice that often results induplicated code. Magic literals are one of the oldest programming ailments.

Smell: Magic LiteralsDetecting the SmellProbably the best way to start out is to analyze code visually or by performing a text search. Once you have spotted a literal, you can use IDE’s text search to look forother occurrences of the same value. Often you will come upon a magic literal valuewhile modifying the code: you should use that opportunity to replace the value witha constant.

Related RefactoringUse Replace Magic Literal with Constant refactoring to eliminate this smell.

236

Part III: Getting Started with Standard Refactoring Transformations

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 236

Page 272: Professional Refactoring in Visual Basic

Magic LiteralsMagic literals are hard-coded values of numbers and strings that are disseminated liberally inside the code.These values have a special meaning with regard to program logic, but when they are placed as literals justanywhere in the code, the purpose of these values becomes less than obvious. In order to make the purposeof these variables explicit, you should give them names.

In Visual Basic you can declare constant values by means of the Const keyword. These values are guar-anteed not to change during program runtime. In some cases constant values can be logically groupedtogether; in these cases enumerations can be used instead of constants.

To resolve the problem with magic literals, you should replace them with constants or enumerations. Thisway your code will became easier to understand and easier to modify, because the values can be changedin a single location instead of your having to search the code for occurrences of the same literal values.

Refactor! makes this task easy through Introduce Constant refactoring, as you will see in the next section.

As with any other refactoring, you should use good judgment when you apply Replace Magic Literal withConstant refactoring. In some cases it just doesn’t make sense. Look at the code example in the “Refactoring:Replace Magic Literal with Constant” sidebar. In that example I have extracted pi, but have left the number 2inside the code. Both methods are implementations of mathematical formulae, but here the number 2 is justthat, a number inside a formula, and replacing it with a constant wouldn’t make sense.

Continued

Refactoring: Replace Magic Literal with ConstantMotivationMagic literals make your code more difficult to understand. The purpose of literal values has to be understood from the context. Because the use of magic literals leads to duplication, it makes the code more prone to bugs and more difficult to maintain.Instead of replacing a value in a single place, the programmer has to hunt for andreplace each occurrence of the magic literal in the code. Declaring literals as constantscan reduce typographical errors, because the compiler can check a constant or an enu-meration but cannot check a literal value. Finally, declaring literals as constants canlead to an increase in productivity and code less prone to bugs, because autocompletecan be used to search for the value instead of your having to memorize it.

Related SmellsUse this refactoring to eliminate the Magic Literals smell.

RationaleLiteral values disseminated liberally in your code can easily lead to duplication. Thepurpose of such values is not immediately obvious. That can lead to code that is diffi-cult to maintain and increase the likelihood of bugs.

237

Chapter 9: The Method Extraction Remedy for Duplicated Code

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 237

Page 273: Professional Refactoring in Visual Basic

MechanicsAfter you have identified the magic literal, declare a constant of the same type as amagic literal, give it a meaningful name, and initialize it with the same value that themagic literal has. Now replace the literal with the constant. Search in your code forother occurrences of the same magic literal, and if they are used for the same purpose,replace them with the newly created constant as well.

If you come upon magic literal values that are logically related and can be grouped, anenumeration is a better choice for replacement than a constant. Use the Enum keywordto declare the enumeration and to enumerate these values. Now you can replace magicliterals with values from the enumeration.

BeforePublic Class Circle

Private radiusValue As Double

Public Property Radius() As DoubleGet

Return radiusValueEnd GetSet(ByVal value As Double)

radiusValue = valueEnd Set

End Property

Public Function CircumferenceLength() As DoubleReturn 2 * 3.1415 * Radius

End Function

Public Function Area() As DoubleReturn 3.1415 * Radius ^ 2

End Function

End Class After

Public Class CirclePrivate Const PI As Double = 3.1415

Private radiusValue As Double

Public Property Radius() As DoubleGet

Return radiusValueEnd GetSet(ByVal value As Double)

radiusValue = valueEnd Set

238

Part III: Getting Started with Standard Refactoring Transformations

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 238

Page 274: Professional Refactoring in Visual Basic

Introduce Constant Refactoring in Refactor!You can use Introduce Constant refactoring for both numerical and string literal values. In order toinvoke this refactoring, you should place the cursor over the literal values you wish to replace with theconstant. After that, Refactor! will mark all the instances of the same literal in the file you are workingon, as you can see in Figure 9-4.

Figure 9-4

However, Refactor! will not go on to replace all of them with the constant after you click the IntroduceConstant option. Only the originally selected literal is replaced; then the progress indicator is displayed,letting you select which literal values you want to replace with the constant. This is only logical, becausethe same literal values can have very different purposes, and there is no way that Refactor! can guesswhich literals should be replaced and which should not. For example, the value 3.1415 can represent thenumber pi, but it can also represent an “agent commission.” Replacing instances of the literal 3.1415 thatrepresent “agent commission” with the constant pi would clearly be an error.

Now it’s time to see how all that you have seen in this chapter so far can be applied to the Rent-a-Wheelsapplication.

End Property

Public Function CircumferenceLength() As DoubleReturn 2 * PI * Radius

End Function

Public Function Area() As DoubleReturn PI * Radius ^ 2

End Function

End Class

239

Chapter 9: The Method Extraction Remedy for Duplicated Code

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 239

Page 275: Professional Refactoring in Visual Basic

Extract Method and Replace Magic Literal Refactoring in the Rent-a-Wheels Application

After this chapter you will certainly look at the existing Rent-a-Wheels code with different eyes. Even uponsuperficial examination, you can see that the Rent-a-Wheels code suffers from the Event-Handling Blindnesssmell. In short, 100 percent of the code is placed in event-handling routines in different form classes.

Now examine the code further, in search of more unpleasant aromas. Here are the problems I am able toidentify so far:

❑ Event-handling blindness: As I have already mentioned, this special case of Long Methodsmell, so popular with Visual Basic programmers, is the most obvious smell in the Rent-a-Wheels code.

❑ Magic literals: A number of magic literals are present in the code: most notably, the database con-nection string is duplicated inside every routine that needs to make use of the persistence services.

❑ Duplicated code: Many methods contain very similar, sometimes even identical, code. Literalvalues are also duplicated in the code.

❑ Copy-paste programming: There is no doubt that the majority of the code in the Rent-a-Wheelsapplication was written using copy-paste style, resulting in numerous duplications and generalcode proliferation. For those with detective instincts, it will suffice to make a comparison betweenthe comments in some methods. Many were left in their original forms after being pasted, withoutbeing updated to a new context. This is a clear testimony to the copy-paste process.

❑ Comments: Because you performed a Rename refactoring in the previous chapter, the majority ofcomments have already become somewhat obsolete. However, they are still used to explain thepurpose of certain blocks of code inside the method, which is a positive signal that the methodsare too long and that method extraction should be performed.

I know that these smells say a lot about the great deficiencies of the existing code, but I will ask you to bepatient for the moment and not perform any refactorings right now. In the next chapter you are going tolearn a number of refactorings that can greatly simplify the work on method extraction. So leave Rent-a-Wheels as is for one more chapter and know that you’ll come back to it once you have a complete arsenalof refactorings for method extraction at your disposal.

SummaryThe chapter you have just read describes refactorings that can result in important and extensive “small-scale” changes to your code. Fortunately, these are the kind of changes that bring a fast return. The codewill immediately become easier to maintain and understand.

You have also seen some underlying object-oriented principles that can motivate these refactorings. Itouched upon encapsulation, and you have seen the ways it can help you deal with complexity in your code. I started with the simplest kind of encapsulation: a function. Functions are very similar to methods,and all refactorings that can be applied to a function work with methods as well. You have seen how Extract

240

Part III: Getting Started with Standard Refactoring Transformations

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 240

Page 276: Professional Refactoring in Visual Basic

Method refactoring can greatly simplify methods by extracting blocks of code into new, separate methods. I have explained how method extraction can be employed to make methods more granular and reusable, toeliminate duplication, to replace comments, and to make code easier to read.

In this chapter I also dealt with duplication, the most destructive force in the development process.Duplicated code will lead you on the road to unmanageable, bloated code that can be a real nightmarefor any programmer who has to maintain it. Unfortunately, duplication is sometimes used deliberately,as a mechanism of reuse, a double-edged sword if ever there was one. Generally, programmers who usecopy-paste style are unaware of the perils that duplication brings even in the short term, or simply tradecode correctness for some very immediate gains in project schedule.

Magic literals are the last issue I talked about in this chapter. While magic literals are not very complex,the negative impact that they can have on code is very important.

In the next chapter you are going to see a few method-level refactorings that can greatly simplify methoddecomposition and are generally used to prepare the ground for method extraction. With that, you will beready to deal with even the most complex methods, and you will be able to take the first and most impor-tant step in bringing your code out of chaos and into the structured world.

241

Chapter 9: The Method Extraction Remedy for Duplicated Code

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 241

Page 277: Professional Refactoring in Visual Basic

79796c09.qxd:WroxPro 2/23/08 8:23 AM Page 242

Page 278: Professional Refactoring in Visual Basic

Method Consolidation andExtraction Techniques

In the previous chapter, you saw the benefits that can come from keeping the code granular andwell encapsulated on the most basic level, the method level. You also saw the importance of organ-izing the code in the form of small methods with well-chosen names.

When you come across long or poorly structured methods, the most common solution is to performmethod extraction. You have also seen the mechanics behind method extraction, different illustrationsof this refactoring, and how the whole process can be automated with the refactoring tool.

However, in the real world method extraction is seldom straightforward. If the method meritsextraction, that means it was not well written in the first place, so you cannot expect that it willlend itself to extraction easily. Very often you will have to perform a number of preparatory stepsto make method extraction meaningful. These steps may involve the following:

❑ Dealing with temporary variables that stand in the way of method extraction

❑ Regrouping statements so that blocks of code can be extracted in one step

❑ Dealing with temporary variable declaration and initialization statements

In this chapter you will see a number of refactorings that can be used as preliminary steps tomethod extraction. If you are able to apply these refactorings efficiently, you will be able to per-form method extraction on even the most complicated and tangled methods.

Dealing with Temporary VariablesTemporary variables are among the biggest impediments to efficient method extraction. Because theonly place you can reach the local variable is within the method that hosts it, the more temporaryvariables you have in the method, the longer the method has to be.

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 243

Page 279: Professional Refactoring in Visual Basic

While you can perform method extraction by passing local variables as parameters, creating numerousmethods with long parameter lists will do much to improve code clarity. Such code goes against theidea of encapsulation, and you need some more efficient means at your disposal when performingmethod extraction.

In this section you are going to see how you can deal with temporary variables.

❑ The simplest case is that in which the variable reach is only inside the block you are going toextract. In that case the variable becomes local to the newly created method and does not standin the way of method extraction.

❑ In other cases you have to think about ways to avoid passing the variable as a parameter to a method.

❑ In some cases you can inline the variable.

❑ In other cases you can replace it with a query and make the result available to more than one method.

❑ Sometimes even temporary variables can be abused. This is the case when the temporary vari-able holds two or more unrelated values during its lifetime. Such a variable makes the codemuch more difficult to read, because its role depends on context. It also works against methodextraction, because it has a longer reach than necessary. The solution is to split this variable intotwo or more.

The next sections examine these valuable temporary-variable-management techniques in detail.

Move Declaration Near Reference RefactoringVB programmers are used to placing temporary variable declarations at the top of the method. Thisshould improve the readability of the method and make it tidier. However, I am not so sure about thisold piece of wisdom. Let me explain why.

First of all, as you saw in Chapter 7, decision and control structures in VB .NET are scoped. This meansthat by placing all variables you use in a method at the beginning of the method, you are declaring themwith greater visibility than necessary, and I have already discussed the benefits of well-encapsulated code.But such placement has another consequence as well. If the variable is initialized inside the decision orcontrol block and declared outside, you will not be able to move initialization to declaration. (I talk aboutmoving initialization to variable declaration in the next section.)

Second of all, placing variables at the beginning of the method instead of placing them in the part of the method where they are used makes Extract Method refactoring much more difficult to perform. As you saw in the previous chapter, when you extract methods, either manually or using a refactoring tool, you select a contiguous block of code and move it to the extracted method. If you are not able toreach the variable, you will have to declare it as a parameter of the newly extracted method. Had youdeclared the variable where it was used, you would be able to select the variable declaration as well,thus removing the necessity for parameter passing.

Definition: A temporary or local variable is a variable declared inside a method orproperty and visible only inside the containing method or property.

244

Part III: Getting Started with Standard Refactoring Transformations

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 244

Page 280: Professional Refactoring in Visual Basic

I generally declare variables as I need them. I find such code easier to read this way. Because I do notneed to search for code that assigns to a variable, I can understand immediately what value the variableholds. Whenever I see that a method has grown a bit out of proportion, I try to consolidate it by refactor-ing. Finally, once you have finely-grained methods, you will probably end up declaring variables at thebeginning of the method anyway. Finely-grained methods are generally so short they don’t leave youwith any other option.

Continued

Refactoring: Move Declaration Near ReferenceMotivationBy placing the variable declaration far away from the line where it is first referenced,you are making code more difficult to read, because it is not immediately clear how thevariable was initialized.

In addition, declaring variables well ahead of time makes the method more difficult torefactor. If you try to extract a part of the method in which the variable was referenced,you will have to pass the variable as a parameter to a newly extracted method, eventhough the declaration could possibly have been placed inside the extracted block.

Related SmellsUse this refactoring to prepare methods for method extraction and to eliminate theLong Method and Overexposure smells.

MechanicsFind all variables in the method that are declared but not initialized at the same line.After that, look for the line where the variable is first referenced. Possibly the methodis long, and the variable can be enclosed in a more restricted scope, or the line wherethe variable is referenced for the first time belongs to a block that does something dif-ferent from the rest of the method and can be extracted. Cut the line with the variabledeclaration and paste it one line above the one in which the variable was referenced forthe first time.

BeforeSub Main()

‘Variable radius declared at the beginning of the methodDim radius As DoubleDim lengthOfCircumference As DoubleDim center As PointDim pointOnCircumference As Point

Console.WriteLine(“Enter X coordinate” + _“of circle center”)center.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _“of circle center”)center.Y = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter X coordinate “ + _“of some point on circumference”)

245

Chapter 10: Method Consolidation and Extraction Techniques

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 245

Page 281: Professional Refactoring in Visual Basic

pointOnCircumference.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _“of some point on circumference”)pointOnCircumference.Y = CDbl(Console.In.ReadLine())Console.WriteLine(“The length of circle “ + _“circumference is:”)

‘Next section is to be extracted when simplifying methodradius = ((pointOnCircumference.X - center.X) ^ 2 + _(pointOnCircumference.Y - center.Y) ^ 2) ^ (1 / 2)lengthOfCircumference = 2 * 3.1415 * radius

Console.WriteLine(lengthOfCircumference)Console.Read()

End Sub

AfterSub Main()

Dim lengthOfCircumference As DoubleDim center As PointDim pointOnCircumference As Point

Console.WriteLine(“Enter X coordinate” + _“of circle center”)center.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _“of circle center”)center.Y = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter X coordinate “ + _“of some point on circumference”)pointOnCircumference.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _“of some point on circumference”)pointOnCircumference.Y = CDbl(Console.In.ReadLine())Console.WriteLine(“The length of circle “ + _“circumference is:”)

‘Now radius variable declaration can be extracted ‘ together with rest of circumference calculation codeDim radius As Doubleradius = ((pointOnCircumference.X - center.X) ^ 2 + _(pointOnCircumference.Y - center.Y) ^ 2) ^ (1 / 2)lengthOfCircumference = 2 * 3.1415 * radius

Console.WriteLine(lengthOfCircumference)Console.Read()

End Sub

246

Part III: Getting Started with Standard Refactoring Transformations

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 246

Page 282: Professional Refactoring in Visual Basic

Move Declaration Near Reference in Refactor!In order to activate this refactoring, select or place the cursor over the variable whose declaration you wantto see moved near the reference. Once you do that, Refactor! will display an arrow helping you visualizethe destination of the variable declaration once the refactoring is performed. You can see Refactor!’s visualaids in Figure 10-1.

Figure 10-1

Move Declaration Near Reference refactoring leads directly to the next refactoring. In the cases in whichyou have the variable declaration outside and the variable initialization code inside the nested block, to beable to move the initialization to the declaration you have to perform Move Declaration Near Referencerefactoring beforehand. Take a look at the following code:

Public Function CalculateTaxReturn() As DoubleDim taxReturn As Double = 0‘variable “tax” can be declared inside If blockDim tax As DoubleIf Not Me.TaxWaivered Then

tax = 19 * Me.YearlyTotalEnd IftaxReturn = ApplyScale(tax)Return taxReturn

End Function

You are not able to invoke Move Initialization to Declaration refactoring on the variable tax unless youperform a Move Declaration Near Reference refactoring first. The next section takes a more detailed lookat Move Initialization to Declaration refactoring.

247

Chapter 10: Method Consolidation and Extraction Techniques

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 247

Page 283: Professional Refactoring in Visual Basic

Move Initialization to Declaration RefactoringIn VB .NET you can initialize the variable on the same line on which you have declared it; this was notpossible in pre-.NET versions of VB. By placing initialization code as a continuation of the variable dec-laration, you are making the code more compact and easier to read. You do not have to scan the code inthe search of the line that initialized the variable.

More importantly, this refactoring will help you prepare the variable for Replace Temp with Query refac-toring. Replace Temp with Query refactoring can be invoked in Refactor! only if the variable has beeninitialized in the declaration statement.

Sometimes variable initialization might depend on certain conditions. In that case the variable cannot beinitialized on the same line on which it is declared. For example, take a look at variable connection inthe following code extract:

Dim connection As IDbConnectionIf Me.DatabaseProviderImplementation = _ProviderImplementation.MSSql Then

connection = New SqlConnectionElseIf Me.DatabaseProviderImplementation _= ProviderImplementation.Oracle Then

connection = New OracleConnection‘...

In this case it is not possible to initialize the variable connection on the same line on which it wasdeclared. However, if the code for some reason is changed in such a way that conditional initialization is not necessary anymore and the conditions have been eliminated, you can use Move Initialization to Declaration refactoring to consolidate your code. For example, if the code you just saw is changed tolook as follows, you can apply Move Initialization to Declaration:

Dim connection As IDbConnectionconnection = CreateConnection() ‘...

The next section shows how this refactoring is accomplished with Refactor! on Visual Basic code insideVisual Studio.

Refactoring: Move Initialization to DeclarationMotivationBy initializing the variable on the same line on which the variable is declared, you willimprove code readability.

Related SmellsUse this refactoring to prepare methods for method extraction and to eliminate theLong Method and Overexposure smells.

MechanicsFind all variables in the method that are declared but not initialized at the same line. Ifnecessary, perform Move Declaration Near Reference refactoring before consolidatinginitialization.

248

Part III: Getting Started with Standard Refactoring Transformations

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 248

Page 284: Professional Refactoring in Visual Basic

Move Initialization to Declaration in Refactor!You can easily invoke this refactoring in Refactor! by selecting or placing the cursor over the variable inthe variable declaration or variable-initialization statement. You can see Refactor! ready to move initial-ization to declaration in Figure 10-2.

Figure 10-2

Split Temporary Variable RefactoringThe longer the temporary-variable reach in a method, the more intertwined the method is and the morecomplex any method extraction becomes. If the same variable is used inside the method for more thanone purpose, it will have a longer reach than necessary. Further, if you use a temporary variable to holdmore than one unrelated value, the code becomes less comprehensible for the reader.

Continued

Smell: Overburdened Temporary VariableDetecting the SmellA variable that holds more than one unrelated value is generally considered overbur-dened. It is a variable that has been assigned more than once, and it is neither a loopingnor a collecting variable. Sometimes these variables are given generic names likeresult or temp.

The mere existence of overburdened variables can be a good indicator that the methodcould benefit from method extraction.

Related RefactoringUse Split Temporary Variable refactoring to eliminate this smell.

BeforeDim connection As IDbConnectionconnection = CreateConnection() ‘...

AfterDim connection As IDbConnection = CreateConnection() ‘...

249

Chapter 10: Method Consolidation and Extraction Techniques

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 249

Page 285: Professional Refactoring in Visual Basic

Those I’ve mentioned are already very good reasons why a temporary variable in a method should beused for a single purpose, but why are temporary variables used for more than one purpose to beginwith? Programmers sometimes use a single variable for more than one purpose in order to save memoryor to type less. Neither reason really holds water because any gains are surpassed by negative effects.Sometimes convoluted temporary usage is not really intentional. Whatever the origin of the multipurposetemporary variable, the variable should be split, and for each usage a new temporary variable should bedeclared. You can see this technique in the following example:

Public Sub PrintReport()Dim result As DoubleFor Each vehicle As Vehicle In Me.Vehicles

If vehicle.Rented Thenresult += 1

End IfNextPrintJob.PrintLine(“Total vehicles rented:” + result)result = 0For Each rental As Rental In Me.Rentals

result += rental.AmountDueNextPrintJob.PrintLine(“Total amount due:” + result)

End Sub

In this example the variable result first holds a value that represents the number of currently rentedvehicles; later on the same variable holds the total amount of pending payments.

Sometimes a good sign of an overburdened variable is the name chosen for it. If it is some genericterm like result, temp, or var, this may be because the variable is used for more than one purpose,so a more specific name would not fit well. After the variable has been split, newly created and origi-nal variables can be created, with names more in accordance with the single role each variable nowperforms.

Retaining Looping and Collecting VariablesHowever, if a variable has been assigned more than once, this is not automatically a sign that the vari-able should be split. Sometimes, the variable has to be assigned more than once, and that is fine. Typicalcases of such variables are looping variables, for example, an index when iterating an array or a collection.This variable has to be changed with each pass through the cycle. In VB .NET the For Each constructminimizes the necessity for declaring looping variables.

RationaleOverburdened variables are serving more than one role in a method. In a sense theyhave a longer “reach,” making method extraction more difficult and the method moreintertwined. Since the role of the variable is not clear, it is not possible to establish thelink between the name and the purpose of the variable, making the code more complexand more difficult to understand.

250

Part III: Getting Started with Standard Refactoring Transformations

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 250

Page 286: Professional Refactoring in Visual Basic

Another case when assigning a variable more than once occurs is when you need to collect certain values,as when you are concatenating strings or calculating some totals. Take a look at the following code:

Private Function CalculateFundsTotal() As DecimalDim total As DecimalFor Each account As Account In Me.accounts

total += account.BalanceNextReturn total

End Function

In this case, the variable total is a collecting variable, and incrementing it by each cycle helps you calculatethe total balance for all accounts in the collection. In this case, splitting the variable does not make sense.

Continued

Refactoring: Split Temporary VariableMotivationEach temporary variable in a method should have a single role. Otherwise its role canbe understood only from the context, making the code less explicit and more difficultto read.

Such variables have a longer reach, and they make a method extraction much more dif-ficult to perform. By splitting the variable so the variable is really assigned only once(unless it is a collecting or looping variable), you make the purpose of the variablesmuch more explicit and greatly simplify method extraction.

Related SmellsUse this refactoring to eliminate the Overburdened Temporary Variable smell.

MechanicsFind all variables that are assigned more than once. Make sure these are neither collect-ing nor looping variables.

1. Start by renaming the variable at the declaration, until the second assign-ment, with a name that explains well its first usage. Rely on the compilerto discover all occurrences of the variable.

Public Sub PrintReport()Dim vehiclesRented As DoubleFor Each vehicle As Vehicle In Me.Vehicles

If vehicle.Rented ThenvehiclesRented += 1

End IfNextPrintJob.PrintLine(“Total vehicles rented:” + result)result = 0For Each rental As Rental In Me.Rentals

result += rental.AmountDueNextPrintJob.PrintLine(“Total amount due:” + result)

End Sub

251

Chapter 10: Method Consolidation and Extraction Techniques

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 251

Page 287: Professional Refactoring in Visual Basic

2. Change the second assignment to a declaration. Check that the code com-piles correctly.

Public Sub PrintReport()Dim vehiclesRented As DoubleFor Each vehicle As Vehicle In Me.Vehicles

If vehicle.Rented ThenvehiclesRented += 1

End IfNextPrintJob.PrintLine(“Total vehicles rented:” + result)Dim result As Double = 0For Each rental As Rental In Me.Rentals

result += rental.AmountDueNextPrintJob.PrintLine(“Total amount due:” + result)

End Sub

3. Perform Rename refactoring on the newly declared variable so that thename of the second occurrence of the original variable corresponds to itspurpose.

4. Because the variable can be assigned more than twice, repeat this processuntil each usage of the variable is split into its own separately declaredvariable.

BeforePublic Sub PrintReport()

Dim result As DoubleFor Each vehicle As Vehicle In Me.Vehicles

If vehicle.Rented Thenresult += 1

End IfNextPrintJob.PrintLine(“Total vehicles rented:” + result)result = 0For Each rental As Rental In Me.Rentals

result += rental.AmountDueNextPrintJob.PrintLine(“Total amount due:” + result)

End Sub

AfterPublic Sub PrintReport()

‘original variable has been renamedDim vehiclesRented As DoubleFor Each vehicle As Vehicle In Me.Vehicles

If vehicle.Rented ThenvehiclesRented += 1

End If

252

Part III: Getting Started with Standard Refactoring Transformations

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 252

Page 288: Professional Refactoring in Visual Basic

Now I want to look at how Refactor! can be used to split local variables. Refactor! simplifies the wholeprocess and makes it possible to perform this refactoring in a single step.

Split Temporary Variable in Refactor!In order to make this refactoring available in the Refactor! menu, you have to select or place the cursorover the name of the variable in a secondary assignment statement. You can see a variable selected andSplit Temporary Variable refactoring marked in the context menu in Figure 10-3.

Figure 10-3

After the refactoring is performed, the newly created variable name stays selected in the editor throughlinked identifiers, making the job of giving a name to the newly created variable immediately available.That completes your look at Split Temporary Variable refactoring; now you’ll see another refactoringthat can help you remove temporary variables altogether.

Inline Temp RefactoringOne way to deal with temporary variables is to eliminate them altogether. If the variable has beenassigned only once with a simple expression, you can eliminate the temporary variable and use theexpression instead.

NextPrintJob.PrintLine(“Total vehicles rented:” +

vehiclesRented)‘after splitting, new variable has been declaredDim amountDueTotal As Double = 0For Each rental As Rental In Me.Rentals

amountDueTotal += rental.AmountDueNextPrintJob.PrintLine(“Total amount due:” + amountDueTotal)

End Sub

253

Chapter 10: Method Consolidation and Extraction Techniques

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 253

Page 289: Professional Refactoring in Visual Basic

As you might guess, the first motivation for this refactoring comes in the context of Extract Methodrefactoring. It can help you eliminate annoying temporary variables and facilitate method extraction.Another time to inline temporary variables is when they are plainly unnecessary, and the expression can be read as easily as the variable, as in the following example:

Public Function NumberOfVacantVehicles() As IntegerDim numberOfVehicles As IntegernumberOfVehicles = Me.NumberOfVehicles‘...

End Function

In this code the temporary variable numberOfVehicles is not adding any clarity to the code, because the property name Me.NumberOfVehicles reads just as well. You can also come upon a similar situationwhen a temporary variable is used to represent a return value of the method, but the method name is suffi-cient to identify its purpose. Such a method might benefit from temp inlining for the sake of simplicity.

Refactoring: Inline TempMotivationYou can reduce the number of temporary variables in the method by performing InlineTemp refactoring. This is especially useful if the local variable is standing in the way ofmethod extraction. Another good time to inline variables is when they add nothing tocode clarity, because the expression that initializes the variable is just as easily read.

Smell: Superfluous Temporary VariableDetecting the SmellA superfluous temporary variable is generally detected only on close inspection of the method. It is the variable that is assigned a value once by an expression and is usedonce, without any operation on the received value. If you can instead directly use theexpression that gives value to the variable, and the variable is contributing nothing tocode clarity, then you have identified a superfluous variable.

Related RefactoringUse Inline Temp refactoring to eliminate this smell.

RationaleWhen a temporary variable adds nothing to code clarity, it is best to eliminate it. Theway a variable can add to code clarity is through its name. If the expression that givesvalues to a variable is equally descriptive and the variable is not used for any addi-tional operation, then it has no use. Each superfluous symbol in the code means moreunnecessary complexity. Not only that, but a superfluous temporary variable will oftenstand in the way of Extract Method refactoring, so your best choice is to eliminate thevariable altogether.

254

Part III: Getting Started with Standard Refactoring Transformations

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 254

Page 290: Professional Refactoring in Visual Basic

Sometimes a temporary variable is assigned more than once, and the initialization expression lends itselfto inlining. In this case, perform Split Temporary Variable before Inline Temp. Finally, if the variable hasbeen initialized by the result of multiple statements, you can perform Extract Method refactoring on thesestatements so that the variable is initialized by a simple expression and Inline Temp refactoring is thenmade available.

Inline Temp in Refactor!Refactor! will make Inline Temp refactoring available only if the variable has been assigned once and therefactoring can be safely performed. Just select or place the cursor over the variable you want to extract,and you will be able to invoke the refactoring from Refactor!’s context menu. You can see Refactor! readyto perform Inline Temp refactoring in Figure 10-4.

Figure 10-4

Related SmellsUse this refactoring to eliminate the Superfluous Temporary Variable smell (a temporaryvariable that adds nothing to code clarity) and as a step that precedes and facilitatesmethod extraction. (Take a look at Extract Method refactoring in Chapter 9 to see thesmells that can be remedied by Extract Method.)

MechanicsIf the variable has been assigned more than once, perform Split Temporary Variablerefactoring before embarking on Inline Temp refactoring.

BeforePublic Function GetVacancyPercentage() As Double

Dim vacancyPercentage As DoublevacancyPercentage = 100 * _(Me.NumberOfVehicles - Me.NumberOfRented) /

Me.NumberOfVehiclesReturn vacancyPercentage

End Function

AfterPublic Function GetVacancyPercentage() As Double

Return 100 * _(Me.NumberOfVehicles - Me.NumberOfRented) /

Me.NumberOfVehiclesEnd Function

255

Chapter 10: Method Consolidation and Extraction Techniques

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 255

Page 291: Professional Refactoring in Visual Basic

Replace Temp with Query RefactoringSometimes the expression that initializes the temporary variable is not very simple, nor its meaning veryobvious, so inlining the variable would make the code in the end less legible. However, the temporaryvariable stands in the way of method extraction, so you need to find a way to deal with it. One thing thatinstance methods or properties can access easily is another instance method or property, and this is agood hint toward the solution to the problem: you can replace a temporary variable with a read-onlyproperty or a method.

The case for this refactoring is even stronger if the same temporary-variable-initialization code is presentin more than one method. If you replace the temporary variable with a read-only property or a method inmore than one encompassing method, not only will you make methods much more prone to extraction,but you will also reduce duplication in your code. This is a typical technique for a situation in which youwould like to have a temporary variable available to more than one property or a method.

Is It a Method or a Property?If a method returns a single value and has no output parameters, it can easily be changed to a read-onlyproperty and vice-versa. While the difference is mostly syntactical, it is nevertheless important to choosethe right form for the extracted block. So, when replacing the temporary variable with a query, shouldyou choose a property or a method?

In the most cases, the solution comes intuitively. Properties are used to represent data, for example toencapsulate access to fields of an object, while methods are used for sending messages between objects.For more borderline cases, take a look at Table 10-1 for guidance.

Table 10-1: Query As Property or Method

In some cases, though a variable might be assigned more than once, you would still like to replace itwith a query. Use Split Temporary Variable refactoring before Replace Temp with Query refactoring toeliminate any secondary assignment, and perform Replace Temp with Query refactoring afterwards.

If temporary variable-initialization code comprises multiple statements, you can use Extract Methodrefactoring to consolidate the initialization code and perform initialization in a single statement. Afterthat, if you move initialization to declaration, Replace Temp with Query refactoring will become avail-able through Refactor!’s menu. More about Refactor! and Replace Temp with Query refactoring is dis-cussed in the next section.

Use Property Use Method

Value returned is coherent data value Value is the result of conversion, for exampleToInt(...)

No observable side effects to calling the property Possible observable side effects

Successive calls will always return the same result

Successive calls do not necessarily return thesame result

Order of execution does not influence the result Order of execution may influence the result

Call is not computationally costly Call is costly

256

Part III: Getting Started with Standard Refactoring Transformations

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 256

Page 292: Professional Refactoring in Visual Basic

Continued

Refactoring: Replace Temp with QueryMotivationSometimes you can see essentially the same value calculated and assigned to a temporaryvariable in more than one method. In order to reduce the duplication, you can extract thecode into a separate query method or a property. You can follow the same steps beforeperforming method extraction to avoid creating unnecessary parameters.

Related SmellsUse this refactoring to eliminate the Duplicated Code smell and to prepare a methodfor extraction.

MechanicsIf the variable has been assigned more than once, split it before replacing it with a query.

1. Perform Extract Method on the expression that assigns to the variable. Forthe newly created method, make the return type the same as the variable’sand make the method return the value of the expression.

2. Eliminate the variable by replacing it with a method call.

BeforePublic Sub PrintReport()

‘...Dim vacantVehicles As IntegervacantVehicles = Me.NumberOfVehicles - Me.NumberOfRentedPrintJob.PrintLine(“Total vehicles vacant:” + _CStr(vacantVehicles))

‘...End Sub

Public Function GetVacancyPercentage() As DoubleReturn 100 * _(Me.NumberOfVehicles - Me.NumberOfRented) /

Me.NumberOfVehiclesEnd Function

AfterPublic Sub PrintReport()

‘...PrintJob.PrintLine(“Total vehicles vacant:” + _CStr(Me.NumberOfVacantVehicles))‘...

End Sub

Public Function GetVacancyPercentage() As DoubleDim vacancyPercentage As DoublevacancyPercentage = 100 * _

257

Chapter 10: Method Consolidation and Extraction Techniques

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 257

Page 293: Professional Refactoring in Visual Basic

Replace Temp with Query in Refactor!Replace Temp with Query will be available in Refactor! only if the variable is initialized in the declara-tion statement. So to make this refactoring available you might need to perform Move Initialization toDeclaration refactoring beforehand. You can see Replace Temp with Query refactoring selected in theRefactor! menu in Figure 10-5.

Figure 10-5

Method Reorganization HeuristicsIn this chapter you have so far seen a number of refactorings that can help you consolidate your meth-ods and make them prone to extraction. Method extraction is the most common remedy for long andamorphous methods. It is also the most common reason to apply the refactorings in this chapter. Whilethe refactorings I dealt with in this chapter have merits of their own, they are especially useful in thecontext of method extraction. As you become more experienced, you will be able to apply these refac-torings more naturally, and you will learn to recognize patterns that lead to extraction. However, at thispoint you might ask yourself how and where to start the whole process. Here are some hints that mighthelp you confront some more complex methods as you start out.

❑ Search for duplication: If you can get rid of duplication by extracting duplicated code as amethod, do it right away. You will rarely have to go back if you use this step to reduce duplicatecode. Doing this will make an original method a bit shorter.

❑ Analyze comments: Very often, comments are a good hint that certain sections of code belongtogether. However, the original author fell a step short of placing the blocks of code in separate

Me.NumberOfVacantVehicles / Me.NumberOfVehiclesReturn vacancyPercentage

End Function

Private ReadOnly Property NumberOfVacantVehicles() As IntegerGet

Return Me.NumberOfVehicles -Me.NumberOfRentedVehicles

End GetEnd Property

258

Part III: Getting Started with Standard Refactoring Transformations

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 258

Page 294: Professional Refactoring in Visual Basic

methods. You can go one step further and use methods to mark the blocks instead of comments.Again, this step will make the original method a bit shorter.

❑ Search for sections of the method that stick well together and have a greater chance of reuse:When you are confronted with long methods, you will often find sections that can be extractedas separate methods. Make the original method thinner by extracting these sections into sepa-rate methods.

As you go through these steps, you will often have to shuffle the code in a method in order to deal withtemporary variables. Move initialization near the reference, split temporary variables, inline them, orreplace them with queries as the need arises. With practice you will soon become more confident andable to apply the techniques you have seen in this chapter. As you go along you will see how your codebecomes more expressive and leaner, and contains less duplication. When you finish with one cycle, goback for another iteration, and another, until you are finally satisfied.

These are more or less the heuristics I tried to apply to the Rent-a-Wheels application. In the next sectionI discuss the latest refactorings applied to this application.

Method Reorganization and Rent-a-WheelsI ended Chapter 9 with an analysis of the problems present in the Rent-a-Wheels application that can be identified based on the smells I had explained so far. I listed and explained the smells I identified inthe application. If you remember, the smells I identified in the Rent-a-Wheels code were:

❑ Event-handling blindness

❑ Magic literals

❑ Duplicated code

❑ Copy-paste programming

❑ Comments

Let me now illustrate these smells with examples in the code. I have found a single method from theRent-a-Wheels application (Listing 10-1) that neatly sums up all the smells I just mentioned. I have usedcomments in bold to mark these smells in the listing.

Listing 10-1: Button Save Click Event-Handling Code in Branch Maintenance Form

‘Event-handling blindness – event handler as a sole functionPrivate Sub Save_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles Save.Click‘Declare variablesDim connection As IDbConnectionDim command As IDbCommand

‘Connection String as Magic Literal‘Create SqlConnectionconnection = New SqlConnection(“Data Source=teslateam;” + _“Initial Catalog=RENTAWHEELS;User ID=RENTAWHEELS_LOGIN;” + _

Continued

259

Chapter 10: Method Consolidation and Extraction Techniques

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 259

Page 295: Professional Refactoring in Visual Basic

Listing 10-1: Button Save Click Event-Handling Code in Branch Maintenance Form (continued)

“Password=RENTAWHEELS_PASSWORD_123”)command = New SqlCommandDim insertBranchSql As StringIf (Me.Id.Text.Equals(“”)) Then

‘Create Sql String with parameter @SelectedLPinsertBranchSql = “Insert Into Branch (Name) “ + _“Values(@Name)“‘Adding command parameter in provider-neutral mannerDim name As IDbDataParameter = command.CreateParameter()name.ParameterName = “@Name”name.DbType = DbType.Stringname.Value = BranchName.Text.ToStringcommand.Parameters.Add(name)

Else‘Create Sql String with parameter @SelectedLPinsertBranchSql = “Update Branch Set Name = @Name “ + _“Where BranchId = @Id”‘Duplicated code – parameter adding statements are repeated‘Adding command parameter in provider-neutral mannerDim name As IDbDataParameter = command.CreateParameter()name.ParameterName = “@Name”name.DbType = DbType.Stringname.Value = BranchName.Text.ToStringcommand.Parameters.Add(name)‘Copy-Paste programming: look at duplicated comments below‘Adding command parameter in provider-neutral mannerDim id As IDbDataParameter = command.CreateParameter()id.ParameterName = “@Id”id.DbType = DbType.Int32id.Value = CInt(Me.Id.Text.ToString)command.Parameters.Add(id)

End If‘Redundant comments‘open connectionconnection.Open()‘Set connection to commandcommand.Connection = connection‘set Sql string to command objectcommand.CommandText = insertBranchSql‘execute commandcommand.ExecuteNonQuery()‘close connectionconnection.Close()BranchMaintenance_Load(Nothing, Nothing)

End Sub

Because the method selected in Listing 10-1 illustrates well a general pattern followed in the Rent-a-Wheelsimplementation, the rest of the code suffers from more or less similar problems. The first smell I attackedwas duplicated code.

260

Part III: Getting Started with Standard Refactoring Transformations

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 260

Page 296: Professional Refactoring in Visual Basic

Removing Duplication in Rent-a-WheelsAfter my first visual inspection I was able to identify a number of event-handling routines containingcompletely identical blocks of code. The first of such blocks was control populating code found in all thenavigational buttons and in the Form_Load routine. Just in case you don’t remember what the BranchMaintenance form looks like, take a look at Figure 10-6.

Figure 10-6

Navigational buttons are small buttons at the bottom of the form that help you move between differentbranch records in the database. Event handlers for all of those buttons, including the Form_Load routine,have a few lines of identical code. In Listing 10-2 I have chosen two such routines for illustration purposes.

Listing 10-2: Navigational Button Event Handlers

Private Sub RightItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles RightItem.Click

If (branches.Rows.Count > currentRowIndex + 1) ThencurrentRowIndex += 1Dim row As DataRow = branches.Rows(currentRowIndex)Me.Id.Text = row.Item(“BranchId”).ToStringBranchName.Text = row.Item(“Name”).ToString

End IfEnd Sub

Private Sub LeftItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles LeftItem.Click

If (currentRowIndex - 1 >= 0 And branches.Rows.Count > 0) ThencurrentRowIndex -= 1Dim row As DataRow = branches.Rows(currentRowIndex)Me.Id.Text = row.Item(“BranchId”).ToStringBranchName.Text = row.Item(“Name”).ToString

End IfEnd Sub

I have marked the identical code in both routines in bold. I decided to extract repeated code in a separatemethod and call it DisplayCurrentRow; take a look at Listing 10-3.

Listing 10-3: The DisplayCurrentRow Extracted Method

Private Sub DisplayCurrentRow()Dim row As DataRow = branches.Rows(currentRowIndex)Me.Id.Text = row.Item(BranchTableIdColumnName).ToStringBranchName.Text = row.Item(BranchTableNameColumnName).ToString

End Sub261

Chapter 10: Method Consolidation and Extraction Techniques

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 261

Page 297: Professional Refactoring in Visual Basic

You have already seen another episode of duplicated code in Listing 10-1. This code has to do with addinga parameter to a database command object. In this case extraction is not so straightforward. While the codeis very similar, there are differences in each section. In order to solve this problem I needed to parameterizethe extracted method. I called the method AddParameter, which receives four parameters. You can see it in Listing 10-4.

Listing 10-4: The AddParameter Extracted Method

Private Sub AddParameter(ByVal command As IDbCommand, _ByVal parameterName As String, ByVal parameterType As DbType, _ByVal paramaterValue As Object)

Dim parameter As IDbDataParameter = command.CreateParameter()parameter.ParameterName = parameterNameparameter.DbType = parameterTypeparameter.Value = paramaterValuecommand.Parameters.Add(parameter)

End Sub

Large portions of the code deal with ADO .Net objects. I extracted the part that creates the connection,sets the connection string to the connection, opens the connection, sets SQL code to the command, andadds a connection to the command to separate the PrepareDataObjects method. Take a look atListing 10-5.

Listing 10-5: The PrepareDataObjects Extracted Method

Private Function PrepareDataObjects(ByVal command As IDbCommand, _ByVal sql As String) As IDbConnection

Dim connection As IDbConnection = _New SqlConnection(ConnectionString)connection.Open()command.Connection = connectioncommand.CommandText = sqlReturn connection

End Function

The last interesting method I extracted is ExecuteNonQuery: its purpose is to execute a command. It isshown in Listing 10-6.

Listing 10-6: The ExecuteNonQuery Extracted Method

Public Sub ExecuteNonQuery(ByVal command As IDbCommand, _ByVal sql As String)

Dim connection As IDbConnection = _PrepareDataObjects(command, sql)command.ExecuteNonQuery()connection.Close()

End Sub

After method extraction, the code has become significantly easier to read. The code was now free of most of the duplication and could not be characterized as being of the copy-paste style anymore. However, somechanges to the code were still pending.

262

Part III: Getting Started with Standard Refactoring Transformations

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 262

Page 298: Professional Refactoring in Visual Basic

Magic Literals, Comments, and Event-Handling Blindness in Rent-a-Wheels

One of the easiest things I’ve had to deal with were comments. After method extraction, most of those comments proved to be redundant, so they could simply be deleted.

Literal values were replaced with constants. For example, a literal string containing branch-deletion SQL code was replaced with a constant, DeleteBranchSql (see Listing 10-7).

Listing 10-7: Literal Value SQL String Replaced with a Constant

Private Const DeleteBranchSql As String = _“Delete Branch Where BranchId = @Id” ‘...Private Sub DeleteBranch()

Dim command As IDbCommand = New SqlCommandAddParameter(command, IdParameterName, DbType.Int32, _CInt(Me.Id.Text))ExecuteNonQuery(command, DeleteBranchSql)

End Sub

I also decided to separate code dealing with the GUI from code dealing with the database (Listing 10-8).As a result, event-handling blindness was done with.

Listing 10-8: Separation of the GUI Automation Code from the Database Code

Private Sub Delete_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Delete.Click

DeleteBranch()BranchMaintenance_Load(Nothing, Nothing)

End Sub

Private Sub DeleteBranch()Dim command As IDbCommand = New SqlCommandAddParameter(command, IdParameterName, DbType.Int32, _CInt(Me.Id.Text))ExecuteNonQuery(command, DeleteBranchSql)

End Sub

As I performed these latest refactorings, I could see that the code was even visually much more pleasing.The longest method is now no more than ten lines long. It can easily be read and debugged. There is noduplication, and there are no unnecessary comments or scattered literals. You can judge the result foryourself. You can see the code for the complete BranchMaintenence form in Listing 10-9. The listingwill also help you understand how all the pieces I just discussed fit together.

Listing 10-9: BranchMaintenence Form Code

Option Explicit OnOption Strict On

Imports System.Data

Continued

263

Chapter 10: Method Consolidation and Extraction Techniques

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 263

Page 299: Professional Refactoring in Visual Basic

Listing 10-9: BranchMaintenence Form Code (continued)

Imports System.Data.SqlClient

Public Class BranchMaintenancePublic Const ConnectionString As String = _“Data Source=teslateam;Initial Catalog=RENTAWHEELS;” + _“User ID=RENTAWHEELS_LOGIN;Password=RENTAWHEELS_PASSWORD_123”

Private Const BranchTableIdColumnName As String = “BranchId”Private Const BranchTableNameColumnName As String = “Name”

Private Const SelectAllFromBranchSql As String = _“Select * from Branch”Private Const DeleteBranchSql As String = _“Delete Branch Where BranchId = @Id”Private Const InsertBranchSql As String = _“Insert Into Branch (Name) Values(@Name)“Private Const UpdateBranchSql As String = _“Update Branch Set Name = @Name Where BranchId = @Id”

Private Const IdParameterName As String = “@Id”Private Const NameParameterName As String = “@Name”

Private branches As DataTablePrivate currentRowIndex As Integer

Private Sub BranchMaintenance_Load(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles MyBase.Load

LoadBranches()If (Me.branches.Rows.Count > 0) Then

currentRowIndex = 0DisplayCurrentRow()

End IfEnd Sub

Private Sub LoadBranches()Dim command As IDbCommand = New SqlCommandDim branches As DataSet = FillDataset(command, SelectAllFromBranchSql)Me.branches = branches.Tables.Item(0)

End Sub

Private Sub RightItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles RightItem.Click

If (branches.Rows.Count > currentRowIndex + 1) ThencurrentRowIndex += 1DisplayCurrentRow()

End IfEnd Sub

Private Sub LeftItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles LeftItem.Click

If (currentRowIndex - 1 >= 0 And branches.Rows.Count > 0) ThencurrentRowIndex -= 1

264

Part III: Getting Started with Standard Refactoring Transformations

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 264

Page 300: Professional Refactoring in Visual Basic

Listing 10-9: BranchMaintenence Form Code (continued)

DisplayCurrentRow()End If

End Sub

Private Sub FirstItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles FirstItem.Click

If (branches.Rows.Count > 0) ThencurrentRowIndex = 0DisplayCurrentRow()

End IfEnd Sub

Private Sub LastItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles LastItem.Click

If (branches.Rows.Count > 0) ThencurrentRowIndex = branches.Rows.Count - 1DisplayCurrentRow()

End IfEnd Sub

Private Sub DisplayCurrentRow()Dim row As DataRow = branches.Rows(currentRowIndex)Me.Id.Text = row.Item(BranchTableIdColumnName).ToStringBranchName.Text = row.Item(BranchTableNameColumnName).ToString

End Sub

Private Sub NewItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles NewItem.Click

Me.Id.Text = “”BranchName.Text = “”

End Sub

Private Sub Save_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Save.Click

SaveBranch()BranchMaintenance_Load(Nothing, Nothing)

End Sub

Private Sub SaveBranch()Dim command As IDbCommand = New SqlCommandIf (Me.Id.Text.Equals(“”)) Then

AddParameter(command, NameParameterName, DbType.String, _BranchName.Text.ToString)ExecuteNonQuery(command, InsertBranchSql)

ElseAddParameter(command, NameParameterName, DbType.String, _BranchName.Text.ToString)AddParameter(command, IdParameterName, DbType.Int32, _CInt(Me.Id.Text.ToString))ExecuteNonQuery(command, UpdateBranchSql)

Continued

265

Chapter 10: Method Consolidation and Extraction Techniques

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 265

Page 301: Professional Refactoring in Visual Basic

Listing 10-9: BranchMaintenence Form Code (continued)

End IfEnd Sub

Private Sub Delete_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Delete.Click

DeleteBranch()BranchMaintenance_Load(Nothing, Nothing)

End Sub

Private Sub DeleteBranch()Dim command As IDbCommand = New SqlCommandAddParameter(command, IdParameterName, DbType.Int32, _CInt(Me.Id.Text))ExecuteNonQuery(command, DeleteBranchSql)

End Sub

Private Sub Reload_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Reload.Click

BranchMaintenance_Load(Nothing, Nothing)End Sub

Private Sub AddParameter(ByVal command As IDbCommand, _ByVal parameterName As String, ByVal parameterType As DbType, _ByVal paramaterValue As Object)

Dim parameter As IDbDataParameter = command.CreateParameter()parameter.ParameterName = parameterNameparameter.DbType = parameterTypeparameter.Value = paramaterValuecommand.Parameters.Add(parameter)

End Sub

Public Sub ExecuteNonQuery(ByVal command As IDbCommand, _ByVal sql As String)

Dim connection As IDbConnection = PrepareDataObjects(command, sql)command.ExecuteNonQuery()connection.Close()

End Sub

Private Function PrepareDataObjects(ByVal command As IDbCommand, _ByVal sql As String) As IDbConnection

Dim connection As IDbConnection = _New SqlConnection(ConnectionString)connection.Open()command.Connection = connectioncommand.CommandText = sqlReturn connection

End Function

Private Function FillDataset(ByVal command As IDbCommand, _ByVal sql As String) As DataSet

Dim connection As IDbConnection = PrepareDataObjects(command, sql)Dim adapter As IDbDataAdapter = New SqlDataAdapter

266

Part III: Getting Started with Standard Refactoring Transformations

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 266

Page 302: Professional Refactoring in Visual Basic

Listing 10-9: BranchMaintenence Form Code (continued)

Dim branches As New DataSetadapter.SelectCommand = commandadapter.Fill(branches)connection.Close()Return branches

End FunctionEnd Class

If you take a look at the rest of the Rent-a-Wheels classes, you will find that a similar pattern was fol-lowed, resulting in much cleaner and more compact code. However, if you carefully inspect the com-plete code, you will probably notice that while there is no more duplication in a single class, there arestill many methods that are duplicated between classes. Just like any other duplicated code, this can’t beright. But don’t worry: the work on Rent-a-Wheels does not end here. In the following chapters you willsee ways to deal with this and other problems that can still be found in the code.

SummaryMethod extraction is arguably the most common refactoring you will perform. It is the step that carries thebrunt in transforming your code from a primordial mess to organized code structures. However, when youare dealing with long, complicated and poorly structured methods, method extraction is seldom straight-forward. To reap the benefits of extraction, you often have to perform a number of preparatory steps.

In this chapter you have seen some simple yet important refactorings that help prepare the code for methodextraction. These refactorings are mostly concerned with solving the problem of temporary variables, thebiggest impediment to effective method extraction.

You have seen how to internally reorganize methods by bringing the declaration near the variable initializa-tion. Because in VB .NET you can initialize the variable in the declaration statement, you have seen how todo this and how it can benefit code legibility.

Sometimes temporary variables have more than one role, making the code more tangled and more diffi-cult to understand and working directly against method extraction, because a long-reaching temporaryvariable has to be passed in and out of the method as a parameter. The solution for overburdened vari-ables is Split Temporary Variable refactoring.

In some cases it is beneficial to eliminate a temporary variable altogether. If the temporary variable hasbeen assigned only once with a simple expression, you can replace the variable with the expression itselfby inlining the temporary variable.

Another way to eliminate a temporary variable is to replace it with query method or property. Because amethod or property can be reached from other methods or properties, a newly created, extracted methodcan use the query instead of parameter-passing. Better yet, if the query code was present in more than onemethod, you can additionally reduce duplication by replacing all occurrences of the repeated expressionwith the query.

267

Chapter 10: Method Consolidation and Extraction Techniques

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 267

Page 303: Professional Refactoring in Visual Basic

Finally, you saw the great impact that method organization had on the Rent-a-Wheels application. Bymy performing method extraction, the code was made much more granular, more legible, and morecompact. Code duplication was reduced to a great extent, and the general maintainability of the applica-tion was greatly improved.

With the refactorings mentioned in this chapter, you will be able to unleash method extraction and torestructure methods so that they are small, highly granular, and encapsulated units that do not containduplicated code. Now, to move forward, you need to bring your techniques to the next level. It is timeobjects came into play. In the next chapters we are going to see how refactorings work on that next levelby performing restructuring from an object-oriented perspective.

268

Part III: Getting Started with Standard Refactoring Transformations

79796c10.qxd:WroxPro 2/23/08 8:25 AM Page 268

Page 304: Professional Refactoring in Visual Basic

Part IV: Advanced Refactorings

Now, in Part IV, you are at the heart of object-oriented design. Here I’ll talk aboutobject-oriented encapsulation, inheritance, polymorphism, genericity, and otherimportant characteristics of object orientation. You will see how to model objects,and the techniques you can employ to turn your analysis artifacts into classes thatwill compose the backbone of your application.

More importantly, you will see how to bring object orientation into the code. Veryoften in Visual Basic,you have to deal with code that mixes presentation, domain, andpersistence logic inside a single persistence class. Such a class can be a Window orWeb Form class. It is important to separate such code and make each of your classesdeal with a single responsibility.

To deal with duplication and other smells in your code, you will have to apply inheri-tance and organize your classes into hierarchies. You’ll see how you can move thefeatures between classes to make your code robust and reusable.

This part is the central one of the book. It deals with some of the most importantrefactorings, and if you are able to apply them successfully, you will be able to produce truly effective object-oriented code.

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 269

Page 305: Professional Refactoring in Visual Basic

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 270

Page 306: Professional Refactoring in Visual Basic

Discovering Objects

In theory, you could write programs in VB .NET without ever using a class or creating an object.Thanks to Visual Basic’s legacy Module construct, you could write your programs in a proceduralstyle without knowing the first thing about object orientation. In practice, such a style is rare, andto be honest I never come across it, except in some ad hoc and demo applications. However, mis-use and poor design of objects are much more common. At the root of such code is a poor under-standing of object orientation.

When designing an object-oriented system, you need to think of it as a system of communicatingand collaborating objects. However, the step of converting analysis artifacts to object-oriented codeis neither trivial nor straightforward and is often crucial for the project. While you have seen in thisbook so far that no change to code is impossible and no design decision is irrevocable, identifyingthe classes for the first time will establish the foundations of your design and will most probablylead the design in certain directions later on.

In this chapter, you’ll cover the following:

❑ I’ll start with a quick overview of object-oriented programming. This will help you under-stand the design techniques and refactorings I’ll talk about in this chapter in a deeper con-text. You will also see how there is more to encapsulation when you’re dealing with objectsthan when you’re dealing with functions.

❑ I will continue to discuss some topics I already touched on in Chapter 8 and Chapter 9. Youwill see how analysis artifacts like user stories or use cases serve as the basis for the design ofthe system. However, the gap between the code and the diagrams and text written in naturallanguage is not easy to bridge. You’ll see some techniques you can employ in this endeavor.

❑ You’ll also see what to do if you stray along the wrong path in designing your code. All isnot lost, and you can use diverse refactoring techniques to consolidate your design.

❑ You’ll see how to convert procedural or database-driven design to object-oriented design.

By the end of this chapter, you will have a better understanding of object-oriented design as adynamic, adaptable creation that can be modified to maximize code quality and to respond tochanging requirements.

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 271

Page 307: Professional Refactoring in Visual Basic

A Quick Object-Oriented Programming Overview

As I indicated in the introduction to the chapter, this first section offers a quick overview of some key object-oriented programming concepts. This is not meant to be in any way comprehensive, and no doubt many of you will be familiar with much of the material. Rather, this section is meant as a quick refresher on somekey concepts/features of object-oriented programming that the refactorings in this chapter involve or takeadvantage of. I have found that such overviews can be useful even for those familiar with the subject inquestion for identifying possible gaps or blind spots in understanding. Once you have identified them, youcan fill in those gaps in order to complete your knowledge on the subject of object-oriented programming.

If you want to head straight to the refactorings discussed in this chapter, you can skip down to the“Designing Classes” section of the chapter and start reading there. However, if you keep reading, youwill refamiliarize yourself with some of the unique characteristics of object-oriented programming thatrefactoring (which is what this book is about) really uses to full advantage.

What Are Objects Anyway?In procedural programming, you write programs by invoking functions and asking them to perform someoperation on the data you generally supply as arguments. Sometimes, those functions return some data as aresult of an operation. In object-oriented programming, on the other hand, you construct systems by sendingmessages to objects and asking them to perform some operations. Objects work as small, independent, andencapsulated machines, each of which has its own piece of responsibility. I have already talked about encap-sulation in Chapter 9, but without talking explicitly about object-oriented programming. I want to start thischapter by taking a look at how encapsulation works with objects.

Encapsulation and ObjectsWith objects, encapsulation works on another level. Here information and operations go together. Objectsare constructed from information representing state and operations that permit access to the information.Thanks to encapsulation, you never access the information directly. The data is hidden, and accessibleonly through properties and methods that provide another internal level of control. These public proper-ties and methods that can be used by other objects to communicate with the object are called an interface.

In that sense, from the outside, objects look like black boxes that expose some functionality through awell-defined interface. However, you have no idea of what goes inside the box or how this functionalityis implemented internally. It also means that the data, implemented as object attributes, can be stored inone form internally and in another form visible to the public.

Take a look at the following Account class code. It illustrates techniques for hiding both information andimplementation.

Public Class Account‘Balance is internally kept as DoublePrivate balanceValue As Double

‘This attribute is not publicly visiblePrivate preferredCustomer As Boolean

‘Balance is publicly visible as String

272

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 272

Page 308: Professional Refactoring in Visual Basic

Public ReadOnly Property Balance() As StringGet

Return balanceValue.ToStringEnd Get

End Property‘...

End Class

In the class Account the field preferredCustomer is not visible from the outside, meaning that thispiece of information is hidden from the public but is still to be used by the object internally. The propertyBalance is visible to the public as a String; however, it is kept internally as a Double. Figure 11-1 illus-trates an object in the form of a machine, in this case a control panel, accessed through an interface. Justas an operator communicates with the machine through the control panel, having no idea as to whathappens inside the machine, so the programmer using only the class has no idea of the inner workingsof an object that programming is instantiating and using.

Figure 11-1

In Chapter 9 I talked about these welcome consequences of encapsulation known as information and implementation hiding. They bring more flexibility to the system, because some internal design andimplementation decisions can be changed without affecting anyone on the outside, thus localizing thechange and preventing its ripple effect. So in the Account example class, you could change the internalimplementation of the Balance property without affecting existing clients. For example, you couldmake use of the FormatCurrency function to take regional settings into account when making Balanceavailable to the public:

‘Total is publicly visible as StringPublic ReadOnly Property Balance() As String

GetReturn FormatCurrency(balanceValue)

End GetEnd Property

This makes the system much easier to maintain. However, the benefits do not end there. In this way representation and information content are separated, so the client is not coupled to a format used inter-nally to represent the information.

Finally, encapsulation is the basis of modularity, which can help you construct complex systems bycombining smaller components. This is crucial for taming complexity, something that object-orientedprogramming can be very good at.

Parts Finished Product

Machine

Control Panel Operator

273

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 273

Page 309: Professional Refactoring in Visual Basic

Before going further, I want take a look at how Refactor! can automate the process of encapsulatingfields by generating property setter and getter definitions. After that, I will return to the overview ofkey object-oriented concepts.

Encapsulate Field Refactoring in Refactor!You can invoke this refactoring by placing the cursor over the field name in the field declaration statement.Refactor! adds the Get and Set part of the property declaration. Get returns the value of the field, whileSet assigns a value to the field. The name that Refactor! chooses for a newly created property will be thesame as that of the field, in uppercase, while the field is renamed with an underscore [_] at the beginning ofits name. The property is declared as Public, while the visibility of the field is changed to Private, if thefield was not private to start with.

Refactor! features two additional refactorings that are essentially read- or write-only variants ofEncapsulate Field refactoring:

❑ Encapsulate Field (read-only) generates the getter method only, creating a read-only property.

❑ Create Setter Method creates a setter method for the field, but does not modify the visibility ofthe method. Write-only properties are much less common than read-only properties, soRefactor! generates an internal Set block with Friend visibility for the method.

You have probably observed that I chose to add the suffix Value to field names in order to differentiate a field name from the encapsulating property name. Since VB is not case-sensitive, field and propertynames have to differ by at least one character. One common convention is to add the underscore prefixto field names, as Refactor! does, but I prefer to use the Value suffix to denote a field encapsulated bythe property of the same name.

You can take a look at Encapsulate Field refactoring selected in the Refactor! menu shown in Figure 11-2.

Figure 11-2

Vehicle

Customer

+HandOver(in customer : Customer)

«enumeration»IdType

1 +owner

* +owned

IdDocument

+Number : String+Type : IdType

274

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 274

Page 310: Professional Refactoring in Visual Basic

Refactoring: Encapsulate FieldMotivationThe reasons for encapsulating a field can be numerous, but the basic one is informationhiding. A property can hide internal data representation from the public view. What’smore, a property can control the data access, validate the data, and inform the objectthat the data is being accessed or modified. A property is also the easiest means ofimplementing read-only data in an object.

Even when a property’s Set and Get blocks do nothing else other than read and write thefield value, you should encapsulate such a field if the class it belongs to is part of the pub-lished interface. Changing a field to a property later on will break binary compatibilitybetween the two versions of the class, so you should avoid such situations early, by usinga property instead of a field. While VB syntax for referencing fields and properties is thesame, CIL syntax for the two differs, resulting in binary incompatibility between versionsof the classes where one exposes a field and the other a property with the same name.

Related SmellsUse this refactoring to eliminate the Overexposure smell and to prepare a class forMove Field refactoring.

Mechanics1. Perform Rename refactoring on the field, adding the suffix Value to the

field name.

2. Create a property with the original name of the field that assigns to andreads from the field.

3. Change the field visibility to private and compile.

BeforePublic Class Account

‘...Public Balance As Decimal

End Class

AfterPublic Class Account‘...Private balanceValue As DecimalPublic Property Balance() As Decimal

GetReturn balanceValue

End GetSet(ByVal value As Decimal)

balanceValue = valueEnd Set

End PropertyEnd Class

275

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 275

Page 311: Professional Refactoring in Visual Basic

Object State RetentionOne very important characteristic of objects is that they retain state during their lifetime. For example, oneclient could call upon an object providing some information. Another client could call upon the same objectlater on, asking for the same information. The result it would receive would be the same information thatthe first client provided. This, of course, is only if the object was internally implemented to provide thesame information, because the clients generally don’t know what happens inside the object, as you learnedin the previous section. Thus the object is capable of preserving its state indefinitely — or at least for as longas it is alive.

This is not so in procedural programming. When the function is invoked, all data (except for global data)lives as long as the function invocation itself. Once the function is executed, the data is discarded, andthere is no trace of the execution left when the function is invoked again. Take a look at the followingcode snippet:

‘...Dim basket As ShoppingBasket = New ShoppingBasketbasket.AddItem(New Product)basket.AddItem(New Product)Console.WriteLine(basket.ItemCount)

Once this code is executed, the console will print 2 as a result. This is thanks to the capacity of the objectbasket to retain state between the calls.

ClassesWhen you program in VB .NET, you design and code classes. Classes have methods, properties, andevents. During runtime, object instances are created based on classes in a process similar to using a moldto create and give shape to some artifact. Once you create an instance from a certain class, you can knowfor sure that this particular object will have the same properties, methods, and events as any other objectever created from that same class.

There is another type of member that is not passed over to instances. Classes can have their own class-level members. These members are known as static in some other languages like C# or Java, but in VB aredenoted by the Shared keyword because all instances (of any class) share them. If one instance updatesthe shared property, all instances will see the updated value. These members behave like any memberdeclared inside the VB module. Members of the VB module are Shared by default, so you do not use theShared keyword when declaring VB-module members.

To create an instance of a class, you use Visual Basic’s New operator. Once the statement containing theNew keyword is executed, a special constructor procedure is executed next. This procedure in VB is knownas Sub New. This makes the Sub New procedure very convenient for some operations related to object creation, such as variable initialization.

Based on what I just said about constructors and shared members, you could easily write code that keepsa count of the instances of some class that are created by means of these two elements. Take a look at thefollowing MyClass code:

Public Class MyClass‘Holds number of created instances of MyClass classPrivate Shared NumberOfInstancesValue As Integer = 0

276

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 276

Page 312: Professional Refactoring in Visual Basic

Public Shared Property NumberOfInstances() As IntegerGet

Return NumberOfInstancesValueEnd GetSet(ByVal value As Integer)

NumberOfInstancesValue = valueEnd Set

End Property

Sub New()‘Increments NumberOfInstances property by one each time ‘an instance of MyClass is createdNumberOfInstances += 1

End Sub‘...

End Class

In this example, you can see how the value of a shared property is incremented by one each time a newinstance of MyClass is created, making this information available to all active objects in the program.

I mentioned earlier that all instances of the same class have the same members, including properties. Most of the time, the values that these properties hold are different from instance to instance. Sometimes,however, it happens that you have two instances that hold identical values for all the properties, making itdifficult for you to tell these objects apart. Nonetheless, Visual Basic’s runtime has no such problems. Thisis thanks to another important characteristic of objects: object identity.

Object IdentityEvery object, even if created from the same class as others and even if the values of all its properties arethe same as those of some other object, is unique in the system and can be told apart by the runtime. Thisis because every object is assigned its own memory space. That means that you can be sure that the handleto an object will point to the same unique object as long as you do not explicitly assign another object to it. You use variables to keep the handle to an object. In case you need two variables to point to the sameobject, you can use the Equals method. Take a look at the following code:

Dim object1 As New ObjectDim object2 As New ObjectConsole.WriteLine(object1.Equals(object2))‘Make object1 and object2 variable point to the same objectobject2 = object1Console.WriteLine(object1.Equals(object2))

If you execute that code, the output will be as follows:

FalseTrue

The default implementation of the Equals method uses reference semantics, meaning that it will returnTrue if the variables point to the same address in the memory.

277

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 277

Page 313: Professional Refactoring in Visual Basic

Sometimes the Equals method can be overridden so that it takes into account the value of properties in theobject. This is called value semantics. It can be useful for value type objects. For example, if you have a classthat represents a date and you have created two instances of that class that represent the same calendar date,say January first, 2008, it is logical that their comparison should yield the result True even if they occupydifferent memory spaces, such as in the following code using Visual Basic’s Date and the .NET Framework’sSystem.DateTime type:

Dim date1 As New DateTime(2008, 1, 1)Dim date2 As New DateTime(2008, 1, 1)Dim date3 As New Date(2008, 1, 1)Console.WriteLine(date1.Equals(date2))Console.WriteLine(date2.Equals(date3))

Execution of the previous code will result in True being displayed on the console window twice.

Objects as Basic Building BlocksIn pre-.NET versions of Visual Basic, you made a strict distinction between primitive types like Integer orString and objects like Collection, ComboBox, and so on. Primitive types were used to hold some datavalues and were often used as basic building blocks when you were writing your own custom classes.

In .NET even simple types like Integer can be treated as fully capable objects. Thanks to the .NET boxingand unboxing feature, the distinctions between simple types, value types, and reference types are almostcompletely blurred. So in .NET it is completely legal to write a code like the following:

Dim stringValue As String‘Initialize string by calling method on a number literalstringValue = 6.ToString

In VB 6, for example, calling a method on a literal representing a primitive was not possible. In .NET thisis possible because value types can be automatically boxed inside an instance of the object reference type.

You should be aware, however, that there is a performance penalty for treating values as reference types.

Root ObjectIn .NET, each time you write a class, it will implicitly inherit the .Net root object: System.Object. Youcan even inherit this object explicitly without any consequence. So whether you declare your class asClass MyClass or as Class MyClass inherits Object, the result is the same: any object you createwill already have a few methods implemented. The public methods are as follows:

❑ Equals

❑ GetHashCode

❑ GetType

❑ ToString

278

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 278

Page 314: Professional Refactoring in Visual Basic

The protected methods are:

❑ Finalize

❑ MemberwiseClone

This is also true for value types, because the base class for value type System.ValueType also inheritsSystem.Object. If you declare a variable as SomeType, the variable will be able to point to an instanceof that or any other subtype of SomeType, because of polymorphic behavior inherent in VB code. In con-sequence, if you declare a variable as Object, because all types in .NET inherit Object, the variable willbe able to point to any instance you can throw at it.

Because System is the default namespace, you are free to omit it and write either System.Object or,which is more common, just Object.

Object Lifetime and Garbage CollectionWhen I talked about object state, I said that objects retain state as long as they are alive. Objects can be aliveas long as the program lives, but this would not be very practical, so the majority are discarded during pro-gram execution once they are not needed anymore. Objects come to life when you call the New operator. Asyou have already seen, the constructor method is called right after object creation. This is straightforward.The next logical question is “When do objects die?” The reply this time is not as simple.

An object’s lifetime is determined by its scope. This means that when the object goes out of scope, theruntime is free to reclaim the memory that the object has been occupying. However, this does not happenimmediately. To understand what happens when an object goes out of scope, you need to understand afew facts about garbage collection.

Garbage collection is a mechanism for automated memory management. To run efficiently, the runtimeneeds to allocate memory for new objects and reclaim that memory once it has been left unused. Once an object has gone out of scope and become unreachable, the garbage collector is free to reclaim thememory space and use it for some other object. Failing to reclaim memory results in memory leakage, aperilous bug well known to COM programmers.

Reference Counting Garbage CollectorPre-.NET Visual Basic programmers could also count on the runtime to liberate memory once theobject was freed. In COM, garbage collection was based upon reference counting. Each time a new ref-erence to an object was created in COM, the reference count was incremented by one. When the refer-ence was removed, the count was decremented. When the count reached zero, the object was removedand the memory reclaimed. Most of the time this worked well. In some situations, as when circularreferences are produced, for example, the count would fail to reach zero, and the memory would neverbe reclaimed. This is partly why VB 6 programmers were accustomed to helping the garbage collectorby setting the variable value to nothing once they had no need for the object, basically manually decre-menting the reference counter. However, this does not work in .NET anymore, because in .NET thegarbage collector is radically different.

279

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 279

Page 315: Professional Refactoring in Visual Basic

Tracing Garbage CollectorIn .NET, Microsoft has implemented a tracing garbage collector. That means that the collector starts atroot objects (local variables and reference object parameters, global and static object pointers, and so on)and then creates the graph of objects that are reachable from the roots. Objects that are not in the graphare considered unreachable, and the collector is free to reclaim the memory.

To make garbage collection more efficient, you can use different collector-optimizing techniques such as dividing objects into generations. This is an optimizing technique used by the .NET garbage collec-tor. Garbage-collector performance is one of the most critical performance aspects of the .NET runtime.However, this is a bit out of the scope of this book, so I will not proceed further with the internals of.NET garbage collection. The important fact about the .NET garbage collector is that it is nondeterminis-tic, meaning that the programmer has no control over when the garbage collection is going to happen.To be precise, it is possible to invoke garbage collection explicitly, but this is not recommended, becauseit means meddling with complex garbage-collection algorithms, and any performance optimization isnot very likely, unless you are quite sure of the implications.

In conclusion, objects are removed from memory when the garbage collector decides it is the best momentto do so. This has one very practical consequence. If you are using some limited resources, like databaseconnections, files, or the like, it may not be practical to wait for the garbage collector to kick in. In thosecases you can use Visual Basic’s Using statement to mark the block after which the object should be dis-posed of. The Using block can be written like this:

Using connection As IDbConnection = _New SqlClient.SqlConnection

‘use connection...End Using

You are guaranteed that the connection object will be disposed of after the End Using statement. In caseyou need to create your own class that should release resources early by means of a Using block, yourclass should implement the System.IDisposable interface.

Garbage collection is a great feature of the .NET Framework. It relieves the programmer of one of themost tedious and bug-spawning tasks, manual memory management.

MessagesAs I mentioned earlier on, the dynamic of the object-oriented system comes from message interchangebetween objects. When you write the code, you send the message to an object, asking it to perform someoperation. This is radically different from calling functions while supplying them with data. As you willsee in the next chapter when I talk about inheritance and polymorphism, when you write the code, youmay not know until runtime the exact function that will be executed as a consequence.

In the previous sections you have seen some of the most important characteristics of object-oriented programming. It is very important to be aware of these characteristics to successfully design and writeobject-oriented code. Obviously, the story of object-oriented programming does not end here. In the nextchapter I’ll talk about inheritance and polymorphism, other crucial mechanisms that any object-orientedenvironment should support.

Now that I have reminded you of the basic building blocks of object-oriented programs, you are ready to embark on the fundamental step in software designing: discovering classes and partitioning your

280

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 280

Page 316: Professional Refactoring in Visual Basic

code in the form of types. You will see how new classes can be created starting from the “clean sheet,”based on your analysis artifacts, or how some existing code can be restructured so new classes are intro-duced in order to better organize the code. One such restructuring is known as Extract Class refactoring.I will start out by discussing the crucial step in the design process by which new classes are formedbased on analysis artifacts.

Designing ClassesIn Chapter 8 I talked about the process of writing software, and about techniques you can use to understandthe problem domain and gather the requirements. You have seen how in that phase of development, severaldiverse artifacts can be produced to help structure and organize knowledge about the requirements andproblem domain, artifacts such as an agreed-upon vocabulary, use cases, and user stories. All these artifactsuse some sort of natural language, diagrams, drawings, prototypes, user interface captures, and so forth tocapture the functionality that the system in question should perform. However, in that chapter I stoppedbefore embarking on the next crucial step — using all this gathered information and transforming it into thecode. Based on these artifacts, you can design the classes that are going to be the basic building blocks ofyour software.

In this section you are going to see some of the most popular approaches for the discovery of objects. Theseapproaches should help you breach a very difficult gap that lies between the natural language and otherartifacts made for human comprehension and the program code, which is written for computer execution.Keep in mind the complexity of the task; these approaches are hardly foolproof. To apply them efficiently,you need a significant amount of experience and practice.

First and foremost, you need to identify objects belonging to a problem domain. Implementation details will be refined as you go along. When you are designing classes, the most important decisions are concernedwith identifying the classes with their operations and attributes and with the relations that classes can have.Another aspect of the system you are designing that you must not overlook is its dynamics — that is, howobjects collaborate and exchange messages.

After a quick look at the first refactoring of this chapter, you will start bridging the gap from concept tocode with the most popular approach, linguistic analysis.

Continued

Refactoring: Extract ClassMotivationLarge classes are difficult to understand and maintain. They also make reuse muchmore difficult and can make you distribute unnecessary pieces of code. Large classesare also a breeding ground for uncontrolled dependencies. Extract Class provides themost direct manner for dealing with large classes.

You should start by looking at the features that are grouped and that go naturallytogether inside the class. Sometimes these features have a common prefix indicating thatthey belong together. For example, in the class Client you have properties prefixed bythe word Telephone: TelephoneNumber, TelephoneAreaCode, TelephoneType. Suchfeatures can be extracted into a separate class.

281

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 281

Page 317: Professional Refactoring in Visual Basic

Related SmellsUse this refactoring to eliminate the Large Class smell and to make a class comply withthe Single Responsibility Principle (SRP). (Note: the Large Class smell and the SRP areboth defined later in this chapter.)

Mechanics1. Create a class for features you would like to split into a separate class.

2. Make a link between an original and a new class. The new class canbecome a field of the originating class.

3. Use Move Field refactoring on the originating class and bring a field intothe new class.

4. Continue moving fields and methods (and rename them if needed aftermoving them) until you are done.

BeforePublic Class ShoppingCart

Private productsValue As IListPrivate customerNumberValue As IntegerPrivate customerNameValue As StringPrivate customerTypeValue As CustomerTypes

Public Function CustomerDiscount() As DecimalIf Me.CustomerType = CustomerTypes.Premium Then

Return 3Else

Return 0End If

End Function

Public Property CustomerType() As CustomerTypesGet

Return customerTypeValueEnd GetSet(ByVal value As CustomerTypes)

customerTypeValue = valueEnd Set

End Property

Public Function CalculateTotal() As DecimalDim total As DecimalFor Each product As Product In Me.Products

total += product.PriceNexttotal = total - ((total / 100) * Me.CustomerDiscount())Return total

End Function

Public Property Products() As IList

282

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 282

Page 318: Professional Refactoring in Visual Basic

Continued

GetReturn productsValue

End GetSet(ByVal value As IList)

productsValue = valueEnd Set

End Property‘...

End Class

AfterPublic Class ShoppingCart

Private productsValue As IListPrivate customerValue As Customer = New Customer()Public Function CalculateTotal() As Decimal

Dim total As DecimalFor Each product As Product In Me.Products

total += product.PriceNexttotal = total - ((total / 100) *

Me.customerValue.Discount())Return total

End FunctionPublic Property Products() As IList

GetReturn productsValue

End GetSet(ByVal value As IList)

productsValue = valueEnd Set

End Property‘...

End Class

Public Class CustomerPrivate NameValue As StringPrivate NumberValue As IntegerPrivate TypeValue As CustomerTypesPublic Property Name() As String

GetReturn NameValue

End GetSet(ByVal value As String)

NameValue = valueEnd Set

End PropertyPublic Property Number() As Integer

GetReturn NumberValue

End GetSet(ByVal value As Integer)

NumberValue = value

283

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 283

Page 319: Professional Refactoring in Visual Basic

Classes Are Nouns, Operations Are VerbsProbably the most popular approach in object-oriented circles for developing code from the artifacts youhave gathered is textual or linguistic analysis. During analysis you have surely produced some text thatdescribes the desired system behavior and sample user-system interactions. Often, this text takes theform of use cases. You can analyze such text and find classes “buried in the text,” along with their mem-bers, according to the following rules:

❑ Nouns become classes.

❑ Verbs become operations.

❑ Some nouns (or adjectives) become attributes of classes.

❑ An “is a” phrase indicates inheritance and a “has a” phrase indicates a strong association suchas composition or aggregation.

You can see how this works on a sample use case from the Rent-a-Wheels application. You have alreadyseen this use case in Chapter 4, and you have many more use cases available in that chapter to give this a try for yourself. This process generally goes through a few steps, and I will demonstrate them in theorder in which they are performed.

Step 1: Mark All the Nouns You start with a very simple operation, marking all nouns in the use case with bold. Here is the Rent aVehicle use case with all the nouns identified:

1. Receptionist selects the vehicle from the list of available vehicles.

2. Receptionist inputs the customer data: ID type and number.

3. Receptionist marks the vehicle as “hand over.”

End SetEnd PropertyPublic Property Type() As CustomerTypes

GetReturn TypeValue

End GetSet(ByVal value As CustomerTypes)

TypeValue = valueEnd Set

End PropertyPublic Function Discount() As Decimal

If Me.Type = CustomerTypes.Premium ThenReturn 3

ElseReturn 0

End IfEnd Function

End Class

284

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 284

Page 320: Professional Refactoring in Visual Basic

Step 2: Eliminate the Actor Generally you do not need to model the actor as a part of your system. The actor is an external entitythat interacts with the system. Actor elimination results in fewer selected nouns.

1. Receptionist selects the vehicle from the list of available vehicles.

2. Receptionist inputs the customer data: ID type and number.

3. Receptionist marks the vehicle as “hand over.”

Step 3: Eliminate Duplicated NounsIn a use case, the same noun can appear more than once. The same noun is usually transformed in thesame class, at least when use cases are well written. So you end up with the following list of nouns:

1. Vehicle

2. List of vehicles

3. Customer

4. ID type

5. ID number

Step 4: Separate System from Domain ClassesThe system classes are those supporting classes that do not belong to the problem domain. Very oftenyou can count on the .NET Framework or third-party libraries to provide you with these classes. If youhave some specific requirements that existing classes do not support, you might need to program theseclasses as well, or extend existing ones. In that case, you should consider them also. Very often, however,the decision to use existing classes or to program custom classes is made later on in the process, whenmore details about implementation are known.

In this example, you can see from the use-case content that “List of vehicles” is an object used for presen-tational purposes, to render a list of vehicles to a user and let that user select a single vehicle from the list.It is probable that some existing widget can be used to instantiate an object that fulfills this purpose, so I’llput aside for the moment the “List of vehicles” noun in this design.

Step 5: Separate Attributes from ClassesIf you read the use case carefully, you will find some compound nouns like ID type and ID number. If youinterpret the second statement in the use case, you will see that ID, in fact, refers to a form of identificationand that ID type and ID number refer to attributes of this class. Some attributes can be successfully repre-sented with system classes. For example, an identification document number can be represented with astring (some documents, like passports, can have characters in the identification number). You could alsorepresent the identification document type with a simple string, but a more elaborate design would limitthe choice of document types. So a better choice for the identification document type is an enumeration.

The rule about using adjectives to identify attributes will not work out well most of the time. Because ofthe style of writing that is common in use cases, you seldom find phrases like this: “The car is red.” Moreoften than not you will find a statement like this: “The color of the car is red.” This means that a noun(color, in this instance) is used in these cases to identify an attribute of a class.

285

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 285

Page 321: Professional Refactoring in Visual Basic

Going back to the example, after this step you have the following classes and enumerations identified:

1. Vehicle

2. Customer

3. ID with attributes number and type

a. Attribute number is of String type

b. Attribute type is of ID type enumeration

4. ID type (enumeration)

Step 6: Interpret “is a” and “has a” PhrasesAgain, you need to do some interpretation of the use case text. You have already seen how ID type andID number were hiding another class not directly mentioned, an ID document. This object is used toidentify the customer.

In fact, you can now say that “the customer has an ID document.” This means that there is some sort ofstronger association between the customer and the ID document. I have chosen to represent this associa-tion as an aggregation, since the customer can probably exist in the system even without an ID document.(The type of association will have no consequence on the code of these classes. It might, however, influ-ence how you program the system later on.) This means that the customer will have a property of thetype ID document. In a class diagram, this is represented with a line going from the IdDocument class to the Customer class with a diamond ending it (see Figure 11-3).

Step 7: Identify the Operations The last sentence in the use case talks about an operation that is performed in the system. You have alreadyidentified the classes in the system, and as you have seen in the introductory part of this chapter, in object-oriented programs you ask an object to perform an operation by sending it a message. Sentence number

Smell: Data ClassDetecting the SmellA class that has only properties and no methods is a data class. These classes and struc-tures are generally easily discovered because of a long list of fields and property blocksuninterrupted by method definitions.

Related RefactoringUse Convert Procedural Design to Objects and Move Method refactorings to eliminate the smell.

RationaleObject-oriented programming brings data and behavior together. A class consisting offields and properties only is lacking methods that give life to data. This behavior isthen implemented in some other place, maybe as a part of some other class or in theform of shared methods. Wherever the methods actually are, by not being in the rightplace they will stand in the way of encapsulation and will make inheritance, polymor-phism, and similar object-oriented techniques impossible to implement.

286

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 286

Page 322: Professional Refactoring in Visual Basic

three in the use case talks about handing over the vehicle. So, you can add a handOver operation to theVehicle class. You need to supply the Vehicle with a piece of information for it to be able to perform thisoperation: you need to supply the Customer. In the end, the operation will look something like this once itis written down:

Public Class Vehicle

Public Sub HandOver(ByVal customer As Customer)‘... to be implemented

End Sub‘...End Class

With this, the process of textual analysis is more or less complete. In real life, you will iterate the process forother use cases. During iteration, you will discover new classes and more members and relations betweenexisting classes. As you move on, more pieces of the picture are discovered, and you move one step closerto the first version of your application.

In the example you analyzed, you found no occurrences of “is a” relation. Such a relation would indicateinheritance between classes and might be written as follows: “If a vehicle is a truck, the hand-over opera-tion is performed as follows.” In that case, you would use another class, Truck, that inherits Vehicle, to represent this new entity. The Truck class would override the handOver operation to realize a differentset of steps that are performed when a truck is handed over to a customer.

Now, take a look at the classes you have identified so for in a diagram (see Figure 11-3).

Figure 11-3

As you can see, the class diagram contains all the classes, their operations and attributes, and the rela-tionships between classes identified in the sample use case by means of textual analysis. If you go backto the Rent-a-Wheels code, you will find no mention of the classes you just identified. From that you canconclude that some other modeling technique was used to design the code. If you remember, the Rent-a-Wheels Visual Basic application layer design was preceded by database design; I call this technique data-base-driven design and will talk about it in the context of anti-patterns. An anti-pattern is similar to a smell,only it generally works on a larger scale.

Customer

Vehicle

+HandOver(in customer : Customer)

«enumeration»IdType

1

+owner

*

+owned IdDocument

+Number : String

+rented by

+rents

+defines type

287

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 287

Page 323: Professional Refactoring in Visual Basic

The next section explores another technique you have at your disposal when you are trying to identifyclasses to comprise the system you are building: thinking about objects in terms of classes, their respon-sibilities, and their collaborators.

Classes, Responsibilities, and CollaboratorsClass-responsibility-collaborator cards (CRC cards) are a brainstorming and design tool that can help youidentify and design the classes for your system. The cards should help you think about classes in termsof their responsibilities and collaborators. This is a great tool for understanding the dynamics of the sys-tem and behavior of objects. The cards render naturally into an object-oriented design.

In a CRC-card design session, you start with a stack of blank 4˝-by-6˝ index cards. Each card shouldrepresent a single class. The cards have three compartments: top, left, and right. Each compartmentshould be filled by the developer.

❑ Top: Place the class name in this compartment. Optionally, add a section for listing super- and subclasses.

❑ Left: List all the responsibilities that belong to this class in this section.

❑ Right: List all the collaborators that the class communicates with to perform its responsibilities.

Definition: An anti-pattern is a repeated solution to a problem that presents a number of difficulties and design flows. A better solution is possible but is notapplied because of lack of knowledge, or simple malpractice. An anti-pattern can generally be refactored into a more optimal solution.

Smell: Database-Driven DesignDetecting the SmellA way to detect this smell is often indirect. Most of the design document is concernedwith database design. The team talks about database structure, data integrity, and nor-malization, but is not very concerned with identifying classes, designing objects, or con-sidering the interactions at work.

Such an approach often leads to a simplistic design wherein Visual Basic code fulfillsthe purpose of a simple data-flow conductor. Because there is no elaborate design,many more advanced object-oriented techniques like inheritance and polymorphismare not present. The design does not reflect the problem domain.

Related RefactoringUse Replace Row with Data Class, Convert Procedural Design to Objects, and ExtractClass refactorings to eliminate the smell.

RationaleCode whose only purpose is to store and retrieve data from the database does not com-ply with basic object-oriented principles and does not represent the problem domain orembody the business rules. Such VB code resigns all important logic to the SQL codelayer. It means that a lot of duplicated and redundant code is present, making theapplication difficult to maintain and modify.

288

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 288

Page 324: Professional Refactoring in Visual Basic

Continued

Refactoring: Move MethodMotivationSometimes a method just seems more interested in other class members than in membersof its own class. In that case, the method makes more sense in the other class.

Related SmellsUse this refactoring to eliminate Large Class, Data Class, and Procedural Design smellsand to simplify classes that do not obey the SRP.

Mechanics1. Start by copying the method into the new class. Rename it if you have a name

that fits better and makes it work with data available in the new class.

2. If the method needs originating class members, and there is no other way toget the necessary functionality, pass the originating object as a parameter toa method.

3. If possible, make the clients use the new method and erase the originalmethod. If not, keep the delegation code in the original method.

Sometimes moving a method might cause other members to be moved. If such a member is used only by the moved method, you can move the other member withoutconsequences.

BeforePublic Class Point

Public X As DoublePublic Y As DoublePublic Function CalculateCircumferenceLength( _ ByVal center As Point, _ByVal pointOnCircumference As Point) As DoubleReturn 2 * 3.1415 * CalculateRadius(center, _pointOnCircumference)End Function

Public Function CalculateRadius(ByVal center As Point, _ByVal pointOnCircumference As Point) As Double

Return ((pointOnCircumference.X - center.X) ^ 2 + _(pointOnCircumference.Y - center.Y) ^ 2) ^ (1 / 2)

End FunctionEnd Class

Public Class CirclePrivate centerValue As PointPrivate pointOnCircumferenceValue As PointPublic Property CenterValue1() As Point

GetReturn centerValue

End Get

289

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 289

Page 325: Professional Refactoring in Visual Basic

Set(ByVal value As Point) centerValue= value

End SetEnd PropertyPublic Property PointOnCircumferenceValue1() As Point

GetReturn pointOnCircumferenceValue

End GetSet(ByVal value As Point)

pointOnCircumferenceValue = valueEnd Set

End PropertyEnd Class

AfterPublic Class Point

Public X As DoublePublic Y As Double

End Class

Public Class CirclePrivate centerValue As PointPrivate pointOnCircumferenceValue As PointPublic Property Center() As Point

GetReturn centerValue

End GetSet(ByVal value As Point)

centerValue = valueEnd Set

End PropertyPublic Property PointOnCircumference() As Point

GetReturn pointOnCircumferenceValue

End GetSet(ByVal value As Point)

pointOnCircumferenceValue = valueEnd Set

End Property

Public Function CalculateCircumferenceLength() As DoubleReturn 2 * 3.1415 * CalculateRadius()

End Function

Public Function CalculateRadius() As DoubleReturn ((Me.PointOnCircumference.X - Me.Center.X) ^ 2

+ _(Me.PointOnCircumference.Y - Me.Center.Y) ^ 2) ^ (1 /

2)End Function

End Class

290

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 290

Page 326: Professional Refactoring in Visual Basic

Moving features between classes is very common in the refactoring process. The following Move Fieldrefactoring is similar to Move Method refactoring (shown in the preceding sidebar) and can be employedin its own right or as a part of some more complex refactorings like Extract Method.

Continued

Refactoring: Move FieldMotivationSometimes methods of some other class seem to be much more interested in the field inthe class than are the methods in the class containing the field. This results in high cou-pling between classes and weakened encapsulation and modularity. While this refactor-ing can be used on its own, this refactoring is an essential part of Extract Classrefactoring.

Related SmellsUse this refactoring to eliminate Large Class, Data Class, and Procedural Design smells.

Mechanics1. Start with Encapsulate Field refactoring, providing property access to a

field, if the field is public and accessed directly. In the originating class,replace all field access with property access.

2. Copy the field and the property that encapsulates it to a targeted class.

3. Make the target class accessible to the originating class. Sometimes you canobtain a target instance from existing fields or methods. If not, you mightneed to add a field with a target instance.

4. Make the property in the originating class use the property on the targetclass instead of on the field.

5. Eliminate the field in the originating class.

Alternatively, if there are only a small number of references to a field in the originatingclass, you can reference a property on the target class directly, eliminating the propertyin the originating class altogether.

BeforePublic Class Rental

‘...Private numberOfDaysValue As IntegerPrivate vehicleValue As VehiclePublic DailyPrice As DecimalPublic Property NumberOfDays() As Integer

GetReturn numberOfDaysValue

End GetSet(ByVal value As Integer)

numberOfDaysValue = valueEnd Set

291

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 291

Page 327: Professional Refactoring in Visual Basic

End PropertyPublic Property Vehicle() As Vehicle

GetReturn vehicleValue

End GetSet(ByVal value As Vehicle)

vehicleValue = valueEnd Set

End PropertyPublic Function Total() As Decimal

Return Me.NumberOfDays * Me.DailyPrice +Vehicle.Tank.Price

End FunctionEnd Class

Public Class VehicleType‘...Private nameValue As StringPublic Property Name() As String

GetReturn nameValue

End GetSet(ByVal value As String)

nameValue = valueEnd Set

End PropertyEnd Class

AfterPublic Class Rental

‘...Private vehicleValue As VehiclePrivate dailyPriceValue As DecimalPublic Property DailyPrice() As Decimal

GetReturn Me.Vehicle.Type.DailyPrice

End GetSet(ByVal value As Decimal)

Me.Vehicle.Type.DailyPrice = valueEnd Set

End PropertyPublic Property NumberOfDays() As Integer

GetReturn numberOfDaysValue

End GetSet(ByVal value As Integer)

numberOfDaysValue = valueEnd Set

End PropertyPublic Property Vehicle() As Vehicle

GetReturn vehicleValue

292

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 292

Page 328: Professional Refactoring in Visual Basic

Employing Cards in Brainstorming SessionsWhen starting a CRC-card design session, you should select a seemingly related set of use cases or user stories. You will first identify the classes in a process similar to textual or linguistic analysis by transformingnouns into classes. Then you will assign responsibilities to the classes. Again, you can use textual analysis toidentify verbs, indicating the problem the class should solve. Finally, you should look for other classes thatthe class needs to successfully fulfill its responsibilities, and you should list these classes in the Collaboratorscompartment on the card.

You generally work with cards in a team environment. As the session progresses, cards are refined, someare put aside, and new ones are created. The cards can be arranged on the table in such an order that differ-ent scenarios can be played out. During this role-playing, each card can represent an instance of a class. Asdifferent scenarios and use cases are played out, the design is further refined and reinforced. If the cardbecomes too crammed with features, it is a good indication that a new card should be introduced and someresponsibilities passed to that new card. Since new cards are easily created and old ones are easily put asideor thrown back into play, cards are a great interactive and dynamic aid. In Figure 11-4 you can see two sam-ple cards created based on the already familiar Rent a Vehicle use case, used for a textual analysis examplein the previous section.

End GetSet(ByVal value As Vehicle)

vehicleValue = valueEnd Set

End PropertyPublic Function Total() As Decimal

Return Me.NumberOfDays * Me.DailyPrice +Vehicle.Tank.Price

End FunctionEnd Class

Public Class VehicleType‘...Private nameValue As StringPrivate dailyPriceValue As DecimalPublic Property Name() As String

GetReturn nameValue

End GetSet(ByVal value As String)

nameValue = valueEnd Set

End PropertyPublic Property DailyPrice() As Decimal

GetReturn dailyPriceValue

End GetSet(ByVal value As Decimal)

dailyPriceValue = valueEnd Set

End PropertyEnd Class

293

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 293

Page 329: Professional Refactoring in Visual Basic

Figure 11-4

CRC cards are great tools for discovering classes that will comprise the design of your system. Theirfocus on responsibilities and object collaboration fits naturally into an object-oriented approach to soft-ware design and can be a great technique for bridging this always-present gap between the analysis artifacts and the code.

Object-Oriented Design Principle: Single ResponsibilityThe Single Responsibility Principle (or SRP) is a fundamental design principle that canhelp you produce a granular and reusable design that is resilient to change. SRP tellsyou that all members of the class should be similar and used to solve a single problem.This way, the motives for change will be much more homogenous and the impact ofchange will be localized.

DefinitionIn Agile Software Development: Principles, Patterns, and Practices (Prentice Hall, 2002),Robert C. Martin writes, “A class should have only one reason to change.”

Again, change is at the heart of this principle. While we cannot fight change in software,it is crucial that the process motivated by change is controlled and localized. The moreheterogeneous the members of the class, the more possible motives for change can befound. This can have huge consequences for application development. Once the respon-sibilities of a class become coupled, the effects of change become far-reaching and diffi-cult to control, resulting in an application that breaks unexpectedly in different places asa result of change.

Executable

ASP PageCOM+

COM+RelationalDatabase

DataLogicPresentation

294

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 294

Page 330: Professional Refactoring in Visual Basic

Continued

Imagine you have a Windows Form class that can calculate the length of a circle’s diame-ter. The class has both presentation and geometrical calculation code. Now imagine thatthe client needs to put this functionality on the web. For this, the presentation class youwill use will be some Active Server Page (ASP) class. Because the initial Windows Formclass has both presentation and calculation code, you’ll have to either completely rewriteyour Windows Form class or distribute unnecessary Windows Form code with an ASPapplication. Now, if your original application changes, you will also have to test the ASP application, distribute the Windows Form class to the ASP application that willnever make any use of this presentation code, version the class, and so on.

ExampleI’ll stick with the familiar subject for this example. Imagine you have programmed aVehicle class as follows:

Public Class VehiclePublic Sub HandOver(ByVal customer As Customer)

If Not Me.InMaintenance ThenMe.HandedOver = True

ElseThrow New InvalidVehicleOperationException( _“Cannot hand over vehicle. Vehicle in maintenance.”)

End IfEnd Sub

Public Sub Update()Dim connection As IDbConnection = _New SqlConnection(ConnectionString)connection.Open()Dim command As IDbCommand = _New SqlCommandCommand.Connection = connectionCommand.CommandText = Sql‘etc..

End Sub‘...

End Class

If you analyze this code, you will clearly see that the Vehicle class has more than one responsibility. It contains both rental business-related code, the HandOver method,and persistence-related code, the Update method. This has a number of possible consequences:

❑ If you change the persistence mechanism, the class has to change.

❑ If you change some business logic, the class has to change.

295

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 295

Page 331: Professional Refactoring in Visual Basic

In the next section, you are going to see another approach to modeling your system, this one more fit for modeling the data than for modeling your classes, but still practiced by many teams when they aredesigning Visual Basic applications.

❑ If one of the responsibilities of the class changes, you have no guaranteethat the other one still works correctly until you test that functionality also.

❑ If you would like to reuse the business-related code, you have to distributepersistence code in the same package and vice versa.

It is often an initial reflex to put persistence logic inside the domain class. After all, the data and the operations with the object go together and should not be separated.However, if you think about it carefully, the Update method is not really concernedwith Vehicle data. It needs this data to be able to persist vehicle instance, for sure,but it is really more concerned with the behavior of the database provider object.

It is clear now that this class should be split into two classes. One should contain only business-related logic and the other only persistence code. As a result, I’ll keepthe Vehicle class and leave the HandOver method inside it. I will create a newVehicleDataStore class and will move the Update method to this new class. The code that follows now obeys the SRP:

Public Class VehiclePublic Sub HandOver(ByVal customer As Customer)

If Not Me.InMaintenance ThenMe.HandedOver = True

ElseThrow New InvalidVehicleOperation( _“Cannot hand over vehicle. Vehicle in maintenance.”)

End IfEnd Sub‘...

End Class

Public Class VehicleDataStorePublic Sub Update(ByVal vehicle As Vehicle)

Dim connection As IDbConnection = _New SqlConnection(ConnectionString)connection.Open()Dim command As IDbCommand = _New SqlCommandcommand.Connection = connectioncommand.CommandText = Sql‘etc..

End Sub‘...

End Class

296

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 296

Page 332: Professional Refactoring in Visual Basic

Entities and RelationshipsThe entity-relationship model is a standard method for designing database structures. This model pres-ents a logical view of the data in which you think about the system in terms of of entities, their attributes,and the relationship between the two. Such a view is easily translated to a relational database design inwhich entities correspond to tables, attributes to columns, and relationships to relational constraints orrelation tables.

Database design is generally motivated by a desire for data retrieval and storage efficiency, data consis-tency, elimination of redundant data, and so on. Often the design is further refined through a normaliza-tion process. Such a process is guided by some mathematical rules that optimize data structuring, and itresembles the refactoring of object-oriented systems.

However, object-oriented design is a design guided by software design principles. You can often findredundant data in object-oriented systems, while this is undesirable from the database design viewpoint.You navigate through objects by following the relations; in databases, you join tables. You can groupobjects inside the collection, while in databases, you store rows inside the table. There are number of different principles that work in one technology, but are not applicable in the other.

This modeling technique can help you visualize some static relations, but the dynamics of the systemand object communication patterns will not be covered. Also, some complex relationships betweenobjects, like inheritance, will not be identified.

If in a project, general application design is driven by database design, you encounter the Database-Driven Design smell. In such cases, the design of classes and Visual Basic code is often neglected andsimply functions as database storage and retrieval. While this can be considered malpractice, it is oftenthe design approach taken by traditional Visual Basic development teams.

You have just seen what to do during the analysis phase. You have seen the techniques you have at yourdisposal to transform the analysis artifacts into the design of your classes. Very often, however, you willalready have at your disposal operational code with all the classes identified and structured already. Inthe next section, you’ll see how to deal with such code.

Object-Relational Impedance MismatchThis term refers to the problem of programs written in some object-oriented languageusing relational databases. The problem arises because the two technologies are notcompletely analogous. While at the first look, there is a correspondence betweenclasses and tables, databases are not capable of expressing more complex relationsbetween classes, like inheritance, that are present in object-oriented systems. As aresult, a lot of boilerplate code has to be produced in order for a database to be usedfor object storage. A number of products, ranging from object-oriented databases toobject-relation mapping tools, have been developed that can deal with this problem to a greater or lesser extent.

297

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 297

Page 333: Professional Refactoring in Visual Basic

Discovering Hidden ClassesIn the first section of this chapter, you saw some basic characteristics of object-oriented code. These char-acteristics should be taken into account when you design your system and write code for your classes. In the second section of this chapter, you saw how you can make a transition from analysis artifactstoward the design of your application.

At this point, you might be wondering what to do with already operational code that is not well structuredand that you sense has other classes that are not identified. In this section, I’ll deal with this very commonsituation: poorly structured code that has classes buried and hidden within it. A typical characteristic ofsuch code is that it does not comply with the SRP.

You will see how to identify overencumbered classes and learn what techniques you have at your dis-posal to refactor such code and distribute the responsibilities between classes in a more balanced way.You can start with a very common situation: code that communicates directly with a database withoutembodying the rules of the problem domain.

Smell: Large ClassDetecting the SmellA long list of properties and methods is the first sign of the Large Class smell, and theeasiest to detect. Another good indicator of a large class is a long and heterogeneousimport section. A large class is often accompanied by long methods.

Related RefactoringUse Extract Method, Extract Class, and Extract Superclass refactorings to eliminate this smell.

RationaleA large class is often a manifestation of some other, more specialized smell like Event-Handling Blindness, Database-Driven Design, or a similar problem caused by mixingpresentation and domain classes. Whatever the motive for its existence, it will suffer fromtoo many responsibilities. That means that the class can suffer change from many differentmotives and is a source of spiraling dependencies and cascading changes.

A large class is are difficult to read and understand. Sometimes only part of its attrib-utes have meaning, and some methods are used under specific circumstances, or itsexecution is conditioned.

Sometimes a large class appears and grows with time. As new functionalities arerequested, the easiest way to implement them is to add more code to existing classes.This is a sign of decaying design, in which not enough care is taken to refactor theapplication in time.

298

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 298

Page 334: Professional Refactoring in Visual Basic

Dealing with Database-Driven DesignA very common approach in database-driven development is to concentrate your design on the database.The data vocabulary is created, tables are designed with data integrity and normalization in mind, andindexes are created for optimal access.

After that, the rest of the application code is developed in sole function for the database. Often, in suchcases, applications are built in the form of a two-tiered architecture wherein the presentation layer is usedto communicate directly with the database, as a sort of data conductor, using the database provider APIdirectly in the presentation classes.

Continued

Refactoring: Replace Row with Data ClassMotivationAn application can be built in the form of a two-tiered architecture, wherein the pres-entation layer communicates directly with the database. Database access and someother APIs often use a row paradigm in the communication layer for retrieval of sets ofdata, such as the System.Data.DataRow class in the .NET Framework.

If such row objects are used, directly evading the use of a business (or domain) layer, thecode will most probably suffer from duplication and will be difficult to understand andmaintain. As a step toward more complete object-oriented design, you can replace rowswith a simple data class that has only properties. Each property should correspond to an item in a row. Such a class contains only data, a signal that the design is not completeand that you should complete it by moving related methods inside the class. You shouldlook at this only as a first step in the right direction: toward defining the domain layerfor your application.

Related SmellsUse this refactoring to eliminate Duplicated Code, Large Class, and Database-DrivenDesign smells and to make classes comply with the SRP.

MechanicsA need for defining the domain layer appears often in two-tiered applications thathave no domain layer. In such applications, presentation and other client layers com-municate directly with the database by means of a row paradigm.

1. Start by creating a class that will represent a row structure. One instance ofa class corresponds to one row.

2. For each item in the row, create a property in your class.

3. Instead of using the rows directly, make the code create instances of thedata class and return them to the client.

Before‘...Public Class AccountView

‘...

299

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 299

Page 335: Professional Refactoring in Visual Basic

Private Sub ViewAccountDetails_Click( _ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles ViewAccountDetails.Click

Dim connection As IDbConnection = _New SqlConnection(ConnectionString)Dim adapter As IDbDataAdapter = New SqlDataAdapterDim accountDataSet As New DataSetDim command As IDbCommand = New SqlCommandDim strSql As String = “Select * from Accounts “ + _“where Name = “ + Me.Number.Textconnection.Open()command.Connection = connectioncommand.CommandText = strSqladapter.SelectCommand = commandadapter.Fill(accountDataSet)connection.Close()accountTable = accountDataSet.Tables.Item(0)Dim accountRow As DataRow = _accountTable.Rows(0)‘Fill controls on the formMe.Name.Text = accountRow.Item(“Name”).ToStringMe.Type.Text = accountRow.Item(“Type”).ToStringIf Not CBool(accountRow.Item(“Blocked”)) Then

Me.Balance.Text = CDec(accountRow.Item(“Balance”))Else

Me.Balance.Text = “Blocked”End If

End SubEnd Class

AfterPublic Class AccountView

‘...Private Sub ViewAccountDetails_Click(ByVal sender AsSystem.Object, _

ByVal e As System.EventArgs) Handles ViewAccountDetails.ClickDim account As Account = GetAccount(Me.Number.Text)

Me.Name.Text = account.NameMe.Type.Text = account.Type

If Not account.Blocked ThenMe.Balance.Text = account.Balance

ElseMe.Balance.Text = “Blocked”

End IfEnd Sub

Private Function GetAccount(ByVal number As String) AsAccount

Dim connection As IDbConnection = _New SqlConnection(ConnectionString)Dim adapter As IDbDataAdapter = New SqlDataAdapterDim accountDataSet As New DataSet

300

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 300

Page 336: Professional Refactoring in Visual Basic

Continued

Dim command As IDbCommand = New SqlCommandDim strSql As String = “Select * from Accounts “ + _“where Name = “ + number

connection.Open()command.Connection = connectioncommand.CommandText = strSqladapter.SelectCommand = commandadapter.Fill(accountDataSet)connection.Close()accountTable = accountDataSet.Tables.Item(0)Dim accountRow As DataRow = _accountTable.Rows(selectedRow)Dim account As New Accountaccount.Number = accountRow.Item(“Number”).ToStringaccount.Name = accountRow.Item(“Name”).ToStringaccount.Type = accountRow.Item(“Type”).ToStringaccount.Balance = CDec(accountRow.Item(“Balance”))account.Blocked = CBool(accountRow.Item(“Blocked”))Return account

End Function‘...

End Class ‘New class defined to replace DataRowPublic Class Account

Private numberValue As StringPrivate nameValue As StringPrivate typeValue As AccountTypePrivate balanceValue As DecimalPublic Property Number() As String

GetReturn numberValue

End GetSet(ByVal value As String)

numberValue = valueEnd Set

End PropertyPublic Property Name() As String

GetReturn nameValue

End GetSet(ByVal value As String)

nameValue = valueEnd Set

End PropertyPublic Property Type() As AccountType

GetReturn typeValue

End GetSet(ByVal value As AccountType)

typeValue = valueEnd Set

301

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 301

Page 337: Professional Refactoring in Visual Basic

Another place to look for hidden objects is in code written in a procedural style. As you probably know,it has taken software science a few decades to move from procedural to object-oriented programming,but in the next section, you will do it in whirlwind fashion.

Moving From Procedural to Object-Oriented DesignAs I have already mentioned, it is possible to write Visual Basic .NET programs without ever coding a classor instantiating an object. The Module construct is a remnant of VB’s procedural origins. Within Moduleyou are free to write global functions and data. Another place for doing things procedurally is in shared(class) methods. You can invoke these methods without creating an instance of a class they belong to. Stillanother sign of procedural design is data without behavior. In VB, such data can be found inside a class orstructure that has only fields and simple properties declared that do nothing other than return the values ofprivate fields.

As I have already mentioned a few times, data can feel lonely without behavior, as can behavior withoutdata. It is only natural to try to bring them together. I will try to illustrate the mechanics with an example.Chapter 9 has an excellent example of procedural style, the CircleCircumferenceLength example. Inthis chapter, you can start just where you left off, at Listing 9-9 in that chapter.

Smell: Procedural DesignDetecting the SmellThis smell is easily detected because of the excessive presence of VB module–level meth-ods and fields. Shared type members have a similar effect to module-level members, sothe excessive use of shared members can also be a sign of procedural design.

Another sign of procedural design is classes or structures comprising properties andno methods (Data Class smell): a type having data but no behavior. Since behavior is not part of the type, it is most probably implemented in the form of shared or VB module functions.

Related RefactoringUse Convert Procedural Design to Objects refactoring to eliminate this smell.

RationaleProcedural design is often a remnant of old styles and practices. When using proceduraldesign, you cannot benefit from inheritance, polymorphism, higher-level encapsulation,or a host of other benefits object-oriented design can bring to your code.

End PropertyPublic Property Balance() As Decimal

GetReturn balanceValue

End GetSet(ByVal value As Decimal)

balanceValue = valueEnd Set

End PropertyEnd Class

302

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 302

Page 338: Professional Refactoring in Visual Basic

The first thing that you can observe is that two methods, CalculateRadius andCalculateCircumferenceLength, have the same parameter list: center and pointOnCircumferencepoints. In this example, these two points are used to define the circle. Because they define a single circle,they should be used as properties of that object. So you can add to your solution a class Circle with twoproperties of type Point: Center and PointOnCircumference. See Listing 11-1.

Listing 11-1: Circle Class Creation

Public Class CirclePrivate centerValue As PointPrivate pointOnCircumferenceValue As PointPublic Property Center() As Point

GetReturn centerValue

End GetSet(ByVal value As Point)

centerValue = valueEnd Set

End PropertyPublic Property PointOnCircumference() As Point

GetReturn pointOnCircumferenceValue

End GetSet(ByVal value As Point)

pointOnCircumferenceValue = valueEnd Set

End PropertyEnd Class

This still a simple data class, but don’t worry — this is just the beginning. Now you should make thecode use this class instead of passing circle points as unrelated objects. Take a look at the Module usingthe Circle class in Listing 11-2.

Listing 11-2: CircleCircumferenceLength After the Circle Class Is Introduced

Module CircleCircumferenceLength

Sub Main()‘Create circle instance and use it to hold circle pointsDim circle As Circle = New Circlecircle.Center = InputPoint(“circle center”)circle.PointOnCircumference = InputPoint(“point on circumference”)Console.WriteLine(“The length of circle “ + _“circumference is:”)Console.WriteLine(CalculateCircumferenceLength(circle))WaitForUserToClose()

End Sub

‘Change the method signature so it uses circle parameter instead of ‘two unrelated points Public Function CalculateCircumferenceLength( _ByVal circle As Circle) As Double

Return 2 * 3.1415 * CalculateRadius(circle)Continued

303

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 303

Page 339: Professional Refactoring in Visual Basic

Listing 11-2: CircleCircumferenceLength After the Circle Class Is Introduced (continued)

End Function

‘Change the method signature so it uses circle parameter Public Function CalculateRadius(ByVal circle As Circle) As Double

Return ((circle.PointOnCircumference.X - circle.Center.X) ^ 2 + _(circle.PointOnCircumference.Y - circle.Center.Y) ^ 2) ^ (1 / 2)

End Function

Public Function InputPoint(ByVal pointName As String) As PointDim point As PointConsole.WriteLine(“Enter X coordinate “ + _“of “ + pointName)point.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _“of “ + pointName)point.Y = CDbl(Console.In.ReadLine())Return point

End Function

Private Sub WaitForUserToClose()Console.Read()

End Sub

End Module

All you have to do now is look for behavior that you can move inside the Circle class. There are twomethods in a module that receive all the data they use as a circle parameter. It would mean less writingif these methods could access the data in the form of instance data instead of as parameters. If you movethese methods inside the Circle class, they can do just that.

To accomplish this, just copy the methods into the Circle class. Then erase the parameter declarationand replace the circle parameter name with the Me keyword if some of the values from the circle are referenced. If the parameter is only referenced directly, simply erase it. The resulting code is shown inListing 11-3.

Listing 11-3: Circle After Move Method Refactoring

Public Class CirclePrivate centerValue As PointPrivate pointOnCircumferenceValue As PointPublic Property Center() As Point

GetReturn centerValue

End GetSet(ByVal value As Point)

centerValue = valueEnd Set

End PropertyPublic Property PointOnCircumference() As Point

Get

304

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 304

Page 340: Professional Refactoring in Visual Basic

Listing 11-3: Circle After Move Method Refactoring (continued)

Return pointOnCircumferenceValueEnd GetSet(ByVal value As Point)

pointOnCircumferenceValue = valueEnd Set

End Property

‘CalculateCircumferenceLength needs no parametersPublic Function CalculateCircumferenceLength() As Double

‘No need to pass circle to CalculateRadius, since ‘CalculateRadius can access all the circle’s propertiesReturn 2 * 3.1415 * CalculateRadius()

End Function

‘CalculateRadius needs no parametersPublic Function CalculateRadius() As Double

Return ((Me.PointOnCircumference.X - Me.Center.X) ^ 2 + _(Me.PointOnCircumference.Y - Me.Center.Y) ^ 2) ^ (1 / 2)

End FunctionEnd Class

All that is left is to make Module use Circle methods instead of its own. This is easily accomplished. AllModule has to do is call the method on the Circle instance. Listing 11-4 shows the final version of the code.

Listing 11-4: CircleCircumferenceLength Calculation Code Converted to Object Design

Option Explicit OnOption Strict On

Namespace RefactoringInVb.Chapter11

Public Structure PointPublic X As DoublePublic Y As Double

End Structure

Module CircleCircumferenceLength

Sub Main()Dim circle As Circle = New Circlecircle.Center = InputPoint(“circle center”)circle.PointOnCircumference = InputPoint(“point on circumference”)Console.WriteLine(“The length of circle “ + _“circumference is:”)Console.WriteLine(circle.CalculateCircumferenceLength())WaitForUserToClose()

End Sub

Public Function InputPoint(ByVal pointName As String) As PointDim point As PointConsole.WriteLine(“Enter X coordinate “ + _

Continued

305

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 305

Page 341: Professional Refactoring in Visual Basic

Listing 11-4: CircleCircumferenceLength Calculation Code Converted to Object Design (continued)

“of “ + pointName)point.X = CDbl(Console.In.ReadLine())Console.WriteLine(“Enter Y coordinate “ + _“of “ + pointName)point.Y = CDbl(Console.In.ReadLine())Return point

End Function

Private Sub WaitForUserToClose()Console.Read()

End Sub

End Module

Public Class CirclePrivate centerValue As PointPrivate pointOnCircumferenceValue As PointPublic Property Center() As Point

GetReturn centerValue

End GetSet(ByVal value As Point)

centerValue = valueEnd Set

End PropertyPublic Property PointOnCircumference() As Point

GetReturn pointOnCircumferenceValue

End GetSet(ByVal value As Point)

pointOnCircumferenceValue = valueEnd Set

End Property

Public Function CalculateCircumferenceLength() As DoubleReturn 2 * 3.1415 * CalculateRadius()

End Function

Private Function CalculateRadius() As DoubleReturn ((Me.PointOnCircumference.X - Me.Center.X) ^ 2 + _(Me.PointOnCircumference.Y - Me.Center.Y) ^ 2) ^ (1 / 2)

End FunctionEnd Class

End Namespace

Here are a couple more interesting items to note in this refactoring.

❑ Because there is no need to access the CalculateRadius method from the outside, you canhide the method by reducing its visibility to private. By doing this, you can witness how withobjects, encapsulation works on another level. The client doesn’t need to know that to calculatelength, you first need to calculate radius.

306

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 306

Page 342: Professional Refactoring in Visual Basic

❑ Also, if you take a look at the Circle class, you will see no data input code inside it. In the examplemodule, you used the Console interface to enable the user to communicate with the application. If you now decide you would prefer to implement a Windows Form interface, you can easily reusethe Circle class. This is possible because domain is separated from presentation, which is the sub-ject of our next section.

Continued

Refactoring: Convert Procedural Design to ObjectsMotivationThe motivation for this refactoring comes from advantages that the object-oriented para-digm provides compared with procedural programming. Object-oriented programmingis a superior, more robust, and more productive paradigm that supersedes proceduralprogramming.

Related SmellsUse this refactoring to eliminate Procedural Design and Data Class smells.

Mechanics1. If you do not have data structures in your code (you are using a DataRow,

for example), start by creating data classes from rows by means of ReplaceRow with Data Class refactoring. If you have data clumps (data that gener-ally goes together but is not structured — for example, repeatable parame-ter lists), start by grouping them in the form of data classes.

2. Move all your procedural code into the single module. Remember, all meth-ods in a module are shared by definition, so you don’t have to use the Sharedkeyword when declaring the methods in a module. This will be handy whenyou move methods to a class.

3. Perform Extract Method on all the long methods in a module to makethem more granular and easier to use.

4. For each method, look for a data class that methods operated on. Thisprobably means that the instance of the data class is passed to a method as a parameter. Move the method to a data class and make it use the dataclass property directly as an instance member, instead of passing it as a parameter.

5. Continue until you move all the methods into classes. Finally, delete themodule from the solution.

BeforePublic Structure ShoppingCart

Public Products As IListEnd Structure

Public Module ShoppingCartFunctionsPublic Function CalculateTotal( _ByVal cart As ShoppingCart) As Decimal

Dim total As Decimal

307

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 307

Page 343: Professional Refactoring in Visual Basic

Keeping Domain, Presentation, and Persistence ApartYou have certainly experienced how Visual Basic’s Rapid Application Development (RAD) features canentice you to confront the problems head-on, coding your way out right from the outset. It is great thatyou can fire up the VB IDE and just fill in the event handlers, but that can also often be a misleading,double-edged advantage that can lead you towards coupled, non-reusable code.

This situation is well known to veteran VB programmers. When the first versions of Visual Basic appeared,the prevailing architectural pattern was two-tiered, a pattern also known as client-server. Relational databasesprovided robust backends, and VB was a tool that permitted a very fast application development. Thencame the Web. Enterprises wished to put their businesses on the Internet. Unfortunately, domain logic cou-pled with presentation inside event-handling routines proved very difficult to reuse, spawning a new archi-tectural style known as three-tiered architecture. Figure 11-5 illustrates three-tier application separation.

Figure 11-5

Executable

ASP PageCOM+

COM+RelationalDatabase

DataLogicPresentation

For Each product As Product In cart.Productstotal += product.Price

NextReturn total

End FunctionEnd Module

AfterPublic Class ShoppingCart

Public Products As IList

Public Function CalculateTotal() As DecimalDim total As DecimalFor Each product As Product In Products

total += product.PriceNextReturn total

End FunctionEnd Class

308

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 308

Page 344: Professional Refactoring in Visual Basic

This separation is often logical and physical, meaning that different tiers reside on different machines.However, as long as the logical separation is correctly performed and the classes are well-structured andorganized, the physical distribution is a less complicated issue.

The tier separation does not end there. Inside the Logic tier, you can have further layering and separationbetween purely domain logic and between persistence code and presentation VB code.

When you have to deal with code in which tier separation is not correctly performed, the best way to startis by separating the domain from the presentation code.

Separating Domain from Presentation CodeThis large-scale refactoring is performed in a few steps. Let’s continue the refactoring example using theAccountView and Account classes from the “Refactoring: Replace Row with Data Class” sidebar earlierin the chapter.

Defining a Data Class for the Domain The first step in separating the domain from the presentation is often Replace Row with Data Classrefactoring. By creating data classes based on table rows, you can often identify the core of the classesthat will comprise your domain layer. This step was already performed in the example shown in the“Refactoring: Replace Row with Data Class” sidebar.

Moving Domain Logic Inside the Data ClassIf you analyze the original AccountView code, you will be able to identify one business rule codedinside an event handler:

If Not account.Blocked ThenMe.Balance.Text = account.Balance

ElseMe.Balance.Text = “Blocked”

End If

You can interpret this rule as follows: “If the account is blocked, hide the account balance.” You willmove this rule to the Account class. For that, you need to modify the Balance property as follows:

Public Property Balance() As DecimalGet

‘use getter to control the Balance accessIf Me.Blocked = True Then

Return 0Else

Return balanceValueEnd If

End GetSet(ByVal value As Decimal)

balanceValue = valueEnd Set

End Property

This way, the balance will stay hidden for anyone using the Account class.

309

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 309

Page 345: Professional Refactoring in Visual Basic

Moving Persistence Logic Inside the Data ClassYou need to keep the presentation layer free of all non-presentation code. For that purpose, you will moveall persistence code to the domain class.

In this example, the GetAccount method is used to read the account data from the database. This methodcan be moved into the Account class and converted into a parameterized constructor. The code for theconstructor looks like this:

Public Sub New(ByVal number As String)Me.Number = numberDim connection As IDbConnection = _New SqlConnection(ConnectionString)Dim adapter As IDbDataAdapter = New SqlDataAdapterDim accountDataSet As New DataSetDim command As IDbCommand = New SqlCommandDim strSql As String = “Select * from Accounts “ + _“where Name = “ + Me.Numberconnection.Open()command.Connection = connectioncommand.CommandText = strSqladapter.SelectCommand = commandadapter.Fill(accountDataSet)connection.Close()accountTable = accountDataSet.Tables.Item(0)Dim accountRow As DataRow = _accountTable.Rows(0)Dim account As New AccountMe.Name = accountRow.Item(“Name”).ToStringMe.Type = New AccountType(accountRow.Item(“Type”).ToString)Me.Balance = CDec(accountRow.Item(“Balance”))Me.Blocked = CBool(accountRow.Item(“Blocked”))

End Sub

Now all the code you have left inside the AccountView class is purely presentation-related. The originalevent handler now looks like this:

Private Sub ViewAccountDetails_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles ViewAccountDetails.Click

Dim account As Account = New Account(Me.Number.Text)Me.Name.Text = account.NameMe.Type.Text = account.TypeIf Not account.Blocked Then

Me.Balance.Text = account.BalanceElse

Me.Balance.Text = “Blocked”End If

End Sub

You have successfully separated persistence from domain code. However, your work is not over yet.Some persistence-related code exists inside your domain class, and such code does not comply with theSRP. You need to move the persistence-related code to a separate class.

310

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 310

Page 346: Professional Refactoring in Visual Basic

Separating Domain from Persistence CodeYou often find designs in which persistence classes contain database access code. In these cases if youneed to change the database, or the persistence method, the changes will affect your domain classes aswell. To avoid this situation, the solution is to extract database code to separate classes. You should startout by defining one data access class for each persisted domain object. Then you should extract methodsand move database access code to newly defined data access classes.

In this example, you will define a new AccountData class. You will move the persistence code from the con-structor to the newly created class. This method can be called GetAccount. Moving this method finishes thetransformation of this example. Take a look at the final version in Listing 11-5.

Listing 11-5: AccountView Example After Separation of Domain and Persistence Code

Option Explicit OnOption Strict On

Public Class ViewAccount‘...Private Sub ViewAccountDetails_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles ViewAccountDetails.Click

Dim account As Account = New Account(Me.Number.Text)Me.Name.Text = account.NameMe.Type.Text = account.TypeIf Not account.Blocked Then

Me.Balance.Text = account.BalanceElse

Me.Balance.Text = “Blocked”End If

End Sub‘...

End Class

Public Class AccountPrivate numberValue As StringPrivate nameValue As StringPrivate typeValue As AccountTypePrivate balanceValue As DecimalPrivate blockedValue As Boolean

Public Sub New(ByVal number As String, _ByVal name As String, ByVal type As AccountType, _ByVal balance As Decimal, ByVal blocked As Boolean)

Me.Number = numberMe.Name = nameMe.Type = typeMe.Balance = balanceMe.Blocked = blocked

End Sub

Public Property Blocked() As BooleanGet

Return blockedValueContinued

311

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 311

Page 347: Professional Refactoring in Visual Basic

Listing 11-5: AccountView Example After Separation of Domain and Persistence Code (continued)

End GetSet(ByVal value As Boolean)

blockedValue = valueEnd Set

End PropertyPublic Property Number() As String

GetReturn numberValue

End GetSet(ByVal value As String)

numberValue = valueEnd Set

End PropertyPublic Property Name() As String

GetReturn nameValue

End GetSet(ByVal value As String)

nameValue = valueEnd Set

End PropertyPublic Property Type() As AccountType

GetReturn typeValue

End GetSet(ByVal value As AccountType)

typeValue = valueEnd Set

End PropertyPublic Property Balance() As Decimal

GetIf Me.Blocked = True Then

Return 0Else

Return balanceValueEnd If

End GetSet(ByVal value As Decimal)

balanceValue = valueEnd Set

End PropertyEnd Class

Public Class AccountData‘...Public Function GetAccount(ByVal number As String) As Account

Dim connection As IDbConnection = _New SqlConnection(ConnectionString)Dim adapter As IDbDataAdapter = New SqlDataAdapterDim accountDataSet As New DataSet

312

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 312

Page 348: Professional Refactoring in Visual Basic

Listing 11-5: AccountView Example After Separation of Domain and Persistence Code (continued)

Dim command As IDbCommand = New SqlCommandDim strSql As String = “Select * from Accounts “ + _“where Name = “ + numberconnection.Open()command.Connection = connectioncommand.CommandText = strSqladapter.SelectCommand = commandadapter.Fill(accountDataSet)connection.Close()accountTable = accountDataSet.Tables.Item(0)Dim accountRow As DataRow = _accountTable.Rows(0)Dim account As New Accountaccount.Number = accountRow.Item(“Number”).ToStringaccount.Name = accountRow.Item(“Name”).ToStringaccount.Type = accountRow.Item(“Type”).ToStringaccount.Balance = CDec(accountRow.Item(“Balance”))account.Blocked = CBool(accountRow.Item(“Blocked”))Return account

End FunctionEnd Class

Sometimes, the code you deal with is so simple that you can extract the domain and persistence layer inparallel. However, your approach depends on your needs:

❑ If you need to free the domain layer from the presentation, start by moving domain and persistencecode out of the presentation.

❑ If you need to liberate the persistence layer, start by forming this layer first.

❑ If your code is simple enough, you can try to do refactorings in parallel.

You’ve covered many significant refactorings in this chapter so far. Now it’s time to turn back to theRent-a-Wheels application and apply that new knowledge to it.

Discovering Objects and the Rent-a-Wheels ApplicationThis content of this chapter promises to introduce some fundamental changes to the Rent-a-Wheelsapplication. On close inspection, you can see that Rent-a-Wheels suffers almost all the maladies youhave encountered in this chapter: large classes; mixed presentation, domain, and persistence code;encumbered classes that are not compliant with SRP; and so on. You need to take following transforma-tions one step at a time. Fortunately, you already know your first step.

Applying Replace Row with Data ClassYou start with the refactoring that is a good starting point in cases like this: Replace Row with DataClass. By looking at the BranchMaintenance form, you can see that you need to declare the Branchclass. Take a look at Listing 11-6 for the Branch class code.

313

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 313

Page 349: Professional Refactoring in Visual Basic

Listing 11-6: Newly Defined Branch Data Class

Option Explicit OnOption Strict On

Public Class Branch

Public Sub New(ByVal id As Integer, ByVal name As String)idValue = idnameValue = name

End Sub

Private idValue As IntegerPrivate nameValue As String

Public Property Id() As IntegerGet

Return idValueEnd GetSet(ByVal value As Integer)

idValue = valueEnd Set

End Property

Public Property Name() As StringGet

Return nameValueEnd GetSet(ByVal value As String)

nameValue = valueEnd Set

End PropertyEnd Class

The Branch class has two properties: a database-generated Id and Name. Also, a convenient parameter-ized constructor is defined so that the class can be instantiated and initialized with data on the same line.

Now, instead of operating directly with DataRow objects in the BranchMaintenance form, you use therow to initialize the instance of the Branch class. You then display the branch object data in the form.Take a look at an example of the use of the branch object in the DisplayCurrentObject method inListing 11-7.

Listing 11-7: Displaying the branch Object in GUI

‘Replaces DisplayCurrentRow method Private Sub DisplayCurrentObject()

Dim branch As Branch = _CType(branches.Item(currentRowIndex), RentAWheel.Branch)Me.Id.Text = branch.Id.ToStringMe.BranchName.Text = branch.Name

End Sub

314

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 314

Page 350: Professional Refactoring in Visual Basic

You need to take a similar approach with the rest of the methods that communicate with the database. Youwill make the rest of the methods use the Branch data class. After that, you need to take care of domainand persistence, and you can start by moving the domain logic into the data class.

Separating Domain from PresentationIn Chapter 4, which introduced the Rent-a-Wheels application, you saw a number of details of vehicleoperation and vehicle state: for example, how the vehicle is from time to time sent to maintenance andhow in that period the vehicle is, as you would expect, taken out of circulation.

In the existing Rent-a-Wheels application, you implement rules regarding vehicle states by manipulat-ing controls on the FleetView form. If the vehicle is in maintenance, then the Hand Over button is dis-abled. This rule belongs to the domain layer, and you need to move this logic inside the Vehicle class.In Listing 11-8 you can see how this is accomplished.

Listing 11-8: Vehicle Data Class Becomes Domain Class

Public Sub HandOver()If Me.Operational Then

Me.RentalState = VehicleRentalState.HandedOverElse

Throw New InvalidVehicleStateException( _“Vehicle in maintenance. Cannot be handed over”)

End IfEnd Sub

Implementation of the maintenance-related rule is simple — if someone tries to hand over a nonopera-tional vehicle, an exception is thrown.

Next, it is time to deal with persistence-related code.

Separating Persistence Code from PresentationTo extract the persistence code from the presentation classes, you define a data access class for each dataclass you have defined. So for the Branch class, you define a new BranchData class. This class holds allthe persistence-related code.

You should start by moving the simplest method, in this case the Delete method, to BranchData. As youmove this method, you have to move the rest of the members this method is referencing. For now, just copythe necessary methods into the BranchData class. As you progress, more and more persistence-related codewill find its way into the BranchData. Once everything is finished, you can erase all the unused persistence-related methods in the BranchMaintenance class. In Listing 11-9, you can see the final result for the branch-related code.

Listing 11-9: Branch-Related Classes: BranchMaintenance, Branch, and BranchData

Option Explicit OnOption Strict On

Imports System.DataImports System.Data.SqlClient

Continued

315

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 315

Page 351: Professional Refactoring in Visual Basic

Listing 11-9: Branch-Related Classes: BranchMaintenance, Branch, and BranchData (continued)

Imports System.CollectionsImports RentAWheel.Data.ColumnNames

Public Class BranchMaintenancePrivate branches As IList = New ArrayListPrivate currentRowIndex As Integer

Dim data As BranchData = New BranchData

Private Sub BranchMaintenance_Load(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles MyBase.Load

LoadBranches()If (Me.branches.Count > 0) Then

currentRowIndex = 0DisplayCurrentObject()

End IfEnd Sub

Private Sub LoadBranches()Me.branches = data.GetAll()

End Sub

Private Sub RightItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles RightItem.Click

If (branches.Count > currentRowIndex + 1) ThencurrentRowIndex += 1DisplayCurrentObject()

End IfEnd Sub

Private Sub LeftItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles LeftItem.Click

If (currentRowIndex - 1 >= 0 And branches.Count > 0) ThencurrentRowIndex -= 1DisplayCurrentObject()

End IfEnd Sub

Private Sub FirstItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles FirstItem.Click

If (branches.Count > 0) ThencurrentRowIndex = 0DisplayCurrentObject()

End IfEnd Sub

Private Sub LastItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles LastItem.Click

If (branches.Count > 0) ThencurrentRowIndex = branches.Count - 1

316

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 316

Page 352: Professional Refactoring in Visual Basic

Listing 11-9: Branch-Related Classes: BranchMaintenance, Branch, and BranchData (continued)

DisplayCurrentObject()End If

End Sub

Private Sub DisplayCurrentObject()Dim branch As Branch = _CType(branches.Item(currentRowIndex), RentAWheel.Branch)Me.Id.Text = branch.Id.ToStringMe.BranchName.Text = branch.Name

End Sub

Private Sub NewItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles NewItem.Click

Me.Id.Text = “”BranchName.Text = “”

End Sub

Private Sub Save_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Save.Click

SaveBranch()BranchMaintenance_Load(Nothing, Nothing)

End Sub

Private Sub SaveBranch()If (Me.Id.Text.Equals(“”)) Then

Dim branch As Branch = New Branch(Nothing, _Me.BranchName.Text.ToString)data.Insert(branch)

ElseDim branch As Branch = New Branch(CInt(Me.Id.Text.ToString), _Me.BranchName.Text.ToString)data.Update(branch)

End IfEnd Sub

Private Sub Delete_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Delete.Click

Dim branch As New Branch(CInt(Me.Id.Text.ToString), _Me.BranchName.Text.ToString)

data.Delete(branch)BranchMaintenance_Load(Nothing, Nothing)

End Sub

Private Sub Reload_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Reload.Click

BranchMaintenance_Load(Nothing, Nothing)End Sub

End Class

Public Class BranchContinued

317

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 317

Page 353: Professional Refactoring in Visual Basic

Listing 11-9: Branch-Related Classes: BranchMaintenance, Branch, and BranchData (continued)

Public Sub New(ByVal id As Integer, ByVal name As String)idValue = idnameValue = name

End Sub

Private idValue As IntegerPrivate nameValue As String

Public Property Id() As IntegerGet

Return idValueEnd GetSet(ByVal value As Integer)

idValue = valueEnd Set

End Property

Public Property Name() As StringGet

Return nameValueEnd GetSet(ByVal value As String)

nameValue = valueEnd Set

End PropertyEnd Class

Public Class BranchData

Public Const ConnectionString As String = _“Data Source=R60;Initial Catalog=RENTAWHEELS;” + _“User ID=RENTAWHEELS_LOGIN;Password=RENTAWHEELS_PASSWORD_123”

Private Const SelectAllFromBranchSql As String = _“Select * from Branch”Private Const DeleteBranchSql As String = _“Delete Branch Where BranchId = @Id”Private Const InsertBranchSql As String = _“Insert Into Branch (BranchName) Values(@Name)“Private Const UpdateBranchSql As String = _“Update Branch Set BranchName = @Name Where BranchId = @Id”

Private Const IdParamterName As String = “@Id”Private Const NameParameterName As String = “@Name”

Public Sub Delete(ByVal branch As Branch)Dim command As IDbCommand = New SqlCommandAddParameter(command, IdParamterName, DbType.Int32, _

318

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 318

Page 354: Professional Refactoring in Visual Basic

Listing 11-9: Branch-Related Classes: BranchMaintenance, Branch, and BranchData (continued)

branch.Id)ExecuteNonQuery(command, DeleteBranchSql)

End Sub

Public Sub ExecuteNonQuery(ByVal command As IDbCommand, _ByVal sql As String)

Dim connection As IDbConnection = PrepareDataObjects(command, sql)command.ExecuteNonQuery()connection.Close()

End Sub

Private Function PrepareDataObjects(ByVal command As IDbCommand, _ByVal sql As String) As IDbConnection

Dim connection As IDbConnection = _New SqlConnection(ConnectionString)connection.Open()command.Connection = connectioncommand.CommandText = sqlReturn connection

End Function

Private Sub AddParameter(ByVal command As IDbCommand, _ByVal parameterName As String, ByVal parameterType As DbType, _ByVal paramaterValue As Object)

Dim parameter As IDbDataParameter = command.CreateParameter()parameter.ParameterName = parameterNameparameter.DbType = parameterTypeparameter.Value = paramaterValuecommand.Parameters.Add(parameter)

End Sub

Public Sub Insert(ByVal branch As Branch)Dim command As IDbCommand = New SqlCommandAddParameter(command, NameParameterName, DbType.String, _branch.Name)ExecuteNonQuery(command, InsertBranchSql)

End Sub

Public Sub Update(ByVal branch As Branch)Dim command As IDbCommand = New SqlCommandAddParameter(command, NameParameterName, DbType.String, _Branch.Name)AddParameter(command, IdParamterName, DbType.Int32, _Branch.Id)ExecuteNonQuery(command, UpdateBranchSql)

End Sub

Public Function GetAll() As IListDim command As IDbCommand = New SqlCommandDim branchesSet As DataSet = FillDataset(command, SelectAllFromBranchSql)

Continued

319

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 319

Page 355: Professional Refactoring in Visual Basic

Listing 11-9: Branch-Related Classes: BranchMaintenance, Branch, and BranchData (continued)

Dim table As DataTable = branchesSet.Tables(0)Dim branches As IList = New ArrayListFor Each row As DataRow In table.Rows

branches.Add(New Branch(CInt(row.Item(BranchTable.Id)), _row.Item(BranchTable.Name).ToString))

NextReturn branches

End Function

Private Function FillDataset(ByVal command As IDbCommand, _ByVal sql As String) As DataSet

Dim connection As IDbConnection = PrepareDataObjects(command, sql)Dim adapter As IDbDataAdapter = New SqlDataAdapterDim branches As New DataSetadapter.SelectCommand = commandadapter.Fill(branches)connection.Close()Return branches

End FunctionEnd Class

This code looks much better. The classes are not encumbered with more responsibilities than necessary,so they do not violate the SRP; presentation, domain, and persistence code are clearly separated; and thesize of the classes is inside the normal limits.

When you try to run the sample code for the chapter, be sure to execute the database modification scriptincluded in the download code. I have renamed a few table columns in order to make the SQL code morecompact. All the code for this book is available at the book’s web site at www.wrox.com.

A very similar pattern is applied to the rest of the Rent-a-Wheels classes. If you take a look at thoseclasses, you will see that many methods between the classes look exactly the same. You have alreadydone a lot of work on eliminating duplication from the code, but it seems that there is still some work to be done. The next chapter will deal with that, too. There you will see how code duplicated betweenclasses can be extracted and duplication eliminated.

SummaryYou covered a lot of ground in this chapter. If someone asked me to select the most important chapter in thebook, I might well name this one. You started by looking at an overview of object-oriented programmingconcepts like encapsulation, object state and identity, object lifetime, garbage collection, and messages.

After looking at objects from a theoretical point of view, you saw analysis techniques you can apply inorder to identify the classes that can be the pillars of your application design.

320

Part IV: Advanced Refactorings

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 320

Page 356: Professional Refactoring in Visual Basic

When you program, you don’t always start out from zero. Very often, as a matter of fact, you need tomanipulate already existing code. In VB code, presentation, domain, and persistence code are mixedvery often. You have seen how you can extract the relevant classes and separate the code. You can oftenstart out by replacing rows with data classes.

Finally, you have once again applied your knowledge to the Rent-a-Wheels application, provoking pro-found changes to its code. Many new classes have been identified, and the code is more evenly spreadout, so that each class takes some responsibility.

In the next chapter, you will encounter some other important characteristics of object-oriented program-ming. You will see how techniques like inheritance, polymorphism, and genericity can be further used toconstruct robust and reusable code.

321

Chapter 11: Discovering Objects

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 321

Page 357: Professional Refactoring in Visual Basic

79796c11.qxd:WroxPro 2/25/08 9:04 AM Page 322

Page 358: Professional Refactoring in Visual Basic

Advanced Object-OrientedConcepts and Related

Refactorings

In the previous chapter, you explored some of the basic concepts in object-oriented programmingtheory. In this chapter, you will work with some more advanced, but by no means optional, charac-teristics of object-oriented programming environments. These are the characteristics that put VisualBasic in the same league as other modern programming languages like C# or Java.

This chapter progresses as follows:

❑ First, you will see how you can employ inheritance, polymorphism, and genericity to furtherimprove the design of your code, remove unnecessary duplication, and enhance encapsula-tion. These capabilities of Visual Basic are fundamental for implementing advanced reusabil-ity mechanisms and for the creation of frameworks and toolkit libraries.

❑ As you have seen many times in this book already, you won’t often reach optimal designduring the first iteration of code. So in the second part of this chapter, you will see howyou can refactor your code by extracting classes and interfaces, moving methods up anddown in the hierarchy, employing generic types, and so on. This way, you will make use of these advanced object-oriented capabilities and thus take the design of your code to thenext level.

❑ At the end of the chapter, you will get back to the Rent-a-Wheels application. You will seehow the number of classes in the application has increased substantially, but the overallcomplexity of the application and even the line count have been reduced.

❑ I will start this chapter by going back to some of the key concepts of object-oriented theory.

79796c12.qxd:WroxPro 2/23/08 8:38 AM Page 323

Page 359: Professional Refactoring in Visual Basic

Inheritance, Polymorphism, and GenericityIt is fair to say that today, Visual Basic lacks no important object-oriented capabilities that other modernobject-oriented languages have. However, pre-.NET versions of Visual Basic contained only interface inheri-tance (I will explain the difference between interface and class inheritance soon enough), making VisualBasic fall into the category of object-based languages and fall short of reaching the object-oriented distinc-tion. This difference is by no means trivial, because implementation inheritance can help you remove impor-tant quantities of duplicated code and enforce desired behavior in inheritance hierarchies. Once VB .NETarrived, the issue was finally settled with the addition of the Inherits keyword to the language.

With the advent of Visual Basic 2005 (VB 8.0), other important features like generics and operator over-loading have been added to the language. This way, Visual Basic keeps evolving and keeping up withother languages as a first-level choice for programming in .NET.

This evolution continues with VB 2008, which adds to the language important features like LINQ, XMLLiterals, Lambda Expressions, Type Inference, Extension Methods, and others.

InheritanceYou have already seen on quite a few occasions refactorings that can be used to reduce code duplication. Youhave seen the devastating consequences that duplicated code can have on the maintainability of your codebase. Until now, though, you haven’t seen any technique that can solve duplication spread between differentclasses. How can you solve a situation in which the same members exist in more than one class?

Imagine you have programmed the class named Customer for a local opera house. This class has typicalproperties and methods like Name, Address, Telephone, PurchaseHistory, and so on. It also has aDiscountPercent method that takes into account the total number of purchases made.

Option Explicit OnOption Strict On

Public Class Customer‘...

Private purchaseHistoryValue As PurchaseHistoryPublic Property PurchaseHistory() As PurchaseHistory

GetReturn purchaseHistoryValue

End GetSet(ByVal value As PurchaseHistory)

purchaseHistoryValue = valueEnd Set

End PropertyPublic Function DiscountPercent() As Decimal

If Me.PurchaseHistory.TotalPurchases > 10 ThenReturn 3

ElseIf Me.PurchaseHistory.TotalPurchases > 100 ThenReturn 5

ElseReturn 0

End IfEnd Function

End Class

324

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:38 AM Page 324

Page 360: Professional Refactoring in Visual Basic

As usual, very soon you have to add some new functionality. Opera management has decided that all senior citizens get a 50 percent discount for all their purchases. At this point, you remember the Open-Closed principle discussed in Chapter 8, and you decide you will not modify the existing Customer class.Instead, you decide to define a new SeniorCitizen class. And you obviously do not want to copy all thecontent of the Customer class into SeniorCitizen; you want to change only the single method.

One option is to declare the Customer class as a property of SeniorCitizen. Then you can delegate allcalls to the already existing Customer members to the Customer property. The only method that containssome new logic is DiscountPercent. Such code would look like this:

Option Explicit OnOption Strict On

Public Class SeniorCitizenPrivate customerValue As CustomerPublic Property Customer() As Customer

GetReturn customerValue

End GetSet(ByVal value As Customer)

customerValue = valueEnd Set

End PropertyPublic Property FirstName() As String

GetReturn Me.Customer.FirstName

End GetSet(ByVal value As String)

Me.Customer.FirstName = valueEnd Set

End Property‘... and so on for every member that Customer declares

Public Function DiscountPercent() As Decimal‘only method that does not delegate to CustomerReturn 50

End FunctionEnd Class

As you can see, this is quite cumbersome and requires a lot of coding. This style was actually used (to a greater or lesser extent) in VB 6, in combination with interface inheritance to simulate implementationinheritance. So here is where class inheritance comes into play. The better solution is simply to inheritthe Customer class and override the DiscountPercent method:

Option Explicit OnOption Strict On

Public Class SeniorCitizenInherits Customer

Public Overrides Function DiscountPercent() As DecimalReturn 50

End Function

325

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:38 AM Page 325

Page 361: Professional Refactoring in Visual Basic

End Class

Public Class Customer‘...

Private purchaseHistoryValue As PurchaseHistoryPublic Property PurchaseHistory() As PurchaseHistory

GetReturn purchaseHistoryValue

End GetSet(ByVal value As PurchaseHistory)

purchaseHistoryValue = valueEnd Set

End Property‘The method has to be declared OverridablePublic Overridable Function DiscountPercent() As Decimal

If Me.PurchaseHistory.TotalPurchases > 10 ThenReturn 3

ElseIf Me.PurchaseHistory.TotalPurchases > 100 ThenReturn 5

ElseReturn 0

End IfEnd Function

End Class

However, the story does not end here. You are again faced with some new requirements. The opera househas also decided to provide walker aids for interested seniors. You can easily describe this requirement bydefining an additional property, RequiresAssistance, in the SeniorCitizen class:

Option Explicit OnOption Strict On

Public Class SeniorCitizenInherits Customer

Private requiresAssistanceValue As Boolean

Public Overrides Function DiscountPercent() As DecimalReturn 50

End Function

Public Property RequiresAssistance() As BooleanGet

Return requiresAssistanceValueEnd GetSet(ByVal value As Boolean)

requiresAssistanceValue = valueEnd Set

End PropertyEnd Class

In this case, the new property is accessible only if the variable is declared and created as SeniorCitizen,as in Dim customer as SeniorCitizen = new SeniorCitizen. If it is declared as Customer, as in

326

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:38 AM Page 326

Page 362: Professional Refactoring in Visual Basic

Dim customer as SeniorCitizen = new Customer, the newly added RequiresAssistance prop-erty will not be accessible. This way the SeniorCitizen class can expose behavior that characterizes onlyitself and not the parent class.

One “overridable” caveat: in Visual Basic, class members are not overrideable by default. This means thatyou should program with extensibility on your mind and always add the overrideable keyword in a memberdeclaration if you suspect that this member might have to be overridden by some subclass in the future.

When the class is inherited, the following things are accomplished automatically:

❑ All the members that belong to the superclass, except private ones, are immediately available tosubclasses as if they were the subclasses’ own.

❑ A subclass automatically implements the interface of the superclass, making the child subclass avalid substitute in polymorphic behavior.

❑ A subclass can, by means of overriding, provide different implementation for certain membersmarked as overrideable in the superclass.

❑ A subclass can provide additional new behavior by defining new members.

As I already mentioned in Chapter 8, if you are not sure about the difference between shadowing andoverriding, take a look at the “Differences Between Shadowing and Overriding” article on MSDN:http://msdn2.microsoft.com/en-us/library/ms172785(VS.80).aspx.

Inheritance is not limited to classes. Interfaces can also be inherited by other interfaces or implementedin a class. This is generally referred to as interface inheritance.

Class Versus Interface InheritanceThe purpose of the Interface construct in Visual Basic is to explicitly define a class interface. You havealready seen how interfaces help encapsulate objects by making objects accessible through a single accesspoint only, and by hiding object internals. Every class has an interface that consists of all public membersof that class. The Interface construct helps you define a group of members that will be visible underthe name of the interface, thus reducing the visible footprint of a class if the class is accessed through an interface.

Sometimes you do not know what the implementation of some interface is going to look like. In thesecases, an interface can serve as a contract that communicating objects have to obey. The object that exposesthe interface guarantees that it is capable of receiving a certain message, and a client object has to send themessage in the exact form defined through its signature in the Interface.

Shadowing Versus OverridingIf you inherit the class and then declare a method or property with the same signature in the subclass, this method will be shadowed by default, as the compiler will warn you.Sometimes shadowing is the desired behavior. In order to override the member, you willhave to use the Overrides keyword. For further explanation on this subtle but crucialdistinction, consult Visual Basic help.

327

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 327

Page 363: Professional Refactoring in Visual Basic

An interface cannot be instantiated and has only abstract members. Abstract members have only a signatureand no implementation. A class implements an interface by means of the Implements keyword. In contrastto a class inheritance, a class can implement multiple interfaces. This is a form of multiple inheritance, apowerful object-oriented concept. When you are inheriting classes, however, only a single inheritance is permitted.

Now you can continue working on the opera example. Define an ICustomer interface and make theclass Customer implement the newly created interface. This way, the Customer class will be visiblethrough a newly created interface whenever a variable pointing to an instance of Customer has beendeclared as ICustomer. The same is true for SeniorCitizen. Because inheritance is transitory, all children implement or inherit all types that their parents implement or inherit. The code with theICustomer interface will, therefore, look like this:

Option Explicit OnOption Strict On

Public Interface ICustomer‘...a few more membersProperty FirstName() As StringProperty PurchaseHistory() As PurchaseHistoryFunction DiscountPercent() As Decimal

End Interface

Public Class CustomerImplements ICustomer‘...Private firstNameValue As StringPrivate purchaseHistoryValue As PurchaseHistory

Public Property FirstName() As String _Implements ICustomer.FirstName

GetReturn firstNameValue

End GetSet(ByVal value As String)

firstNameValue = valueEnd Set

End PropertyPublic Property PurchaseHistory() As PurchaseHistory _Implements ICustomer.PurchaseHistory

GetReturn purchaseHistoryValue

End GetSet(ByVal value As PurchaseHistory)

purchaseHistoryValue = valueEnd Set

End PropertyPublic Overridable Function DiscountPercent() As Decimal _Implements ICustomer.DiscountPercent

If Me.PurchaseHistory.TotalPurchases > 10 ThenReturn 3

ElseIf Me.PurchaseHistory.TotalPurchases > 100 ThenReturn 5

328

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 328

Page 364: Professional Refactoring in Visual Basic

ElseReturn 0

End IfEnd Function

End Class

Public Class SeniorCitizenInherits CustomerPrivate requiresAssistanceValue As BooleanPublic Overrides Function DiscountPercent() As Decimal

Return 50End FunctionPublic Property RequiresAssistance() As Boolean

GetReturn requiresAssistanceValue

End GetSet(ByVal value As Boolean)

requiresAssistanceValue = valueEnd Set

End PropertyEnd Class

Interfaces are closely related to polymorphic behavior. In order to be able to perform type substitution,an object has to implement an interface or inherit the class.

PolymorphismIn the example used so far in this chapter, you have seen how a class can inherit other classes and override some members and how it can implement an interface. But so far you have not seen what happens from the client’s point of view. For that purpose, you can add an additional class to the example,a PurchaseForm class that has a Customer property. You will also add another DisplayForm with twobuttons, InstantiateAsCustomer and InstantiateAsSeniorCitizen, whose sole purpose is to passan instance of Customer and then an instance of SeniorCitizen to PurchaseForm.

Note: The code I have displayed here is programmer-generated code of the PurchaseForm class. AnyWindows Form class is a partial class, and ID-generated code is placed in another file. I have omittedthe IDE-generated code for this example because of space considerations. The complete code for thisexample is available for download from www.wrox.com.

Option Explicit OnOption Strict On

Public Class PurchaseFormPrivate customerValue As ICustomerPublic Property Customer() As ICustomer

GetReturn customerValue

End GetSet(ByVal value As ICustomer)

customerValue = valueEnd Set

329

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 329

Page 365: Professional Refactoring in Visual Basic

End Property

Private Sub PurchaseForm_Activated( _ByVal sender As Object, ByVal e As System.EventArgs) _Handles Me.Activated

‘display discount percent on the formMe.Discount.Text = Me.Customer.DiscountPercent.ToString

End SubEnd Class

Public Class DisplayFormDim purchaseForm As PurchaseForm = New PurchaseFormPrivate Sub InstantiateAsCustomer_Click _(ByVal sender As System.Object, ByVal e As System.EventArgs) _Handles InstantiateAsCustomer.Click

‘create instance of CustomerpurchaseForm.Customer = New CustomerpurchaseForm.Show()purchaseForm.Activate()

End SubPrivate Sub InstantiateAsSeniorCitizen_Click( _ByVal sender As System.Object, ByVal e As System.EventArgs) _Handles InstantiateAsSeniorCitizen.Click

‘create instance of SeniorCitizenpurchaseForm.Customer = New SeniorCitizenpurchaseForm.Show()purchaseForm.Activate()

End SubEnd Class

If you run this example and then:

❑ Press the InstantiateAsCustomer button, the PurchaseForm will show 0 as the discountpercent. (Assume that purchase history is empty by default.)

❑ Press the InstantiateAsSeniorCitizen button, the PurchaseForm will show 50 as the discount percent.

In the first case, the DiscountPercent method implemented in Customer was executed, and in the second, the DiscountPercent method implemented in SeniorCitizen was executed.

This is an example of polymorphic behavior, wherein the same variable can point to different instances of different classes. The same operation was defined and implemented differently in different classes. An important thing to notice is that the exact type of the instance that the variable is pointing to is notknown until runtime.

In this example, this means that you can use the same form and the same code to perform purchasesfor both Customer and SeniorCitizen. The PurchaseForm is oblivious to the implementing type ofthe ICustomer interface. This helps improve the encapsulation of the system and makes reuse evenmore efficient.

330

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 330

Page 366: Professional Refactoring in Visual Basic

Interface Versus Abstract ClassSomewhere between classes and interfaces are abstract classes. These classes in Visual Basic are definedby means of the MustInherit keyword. Neither abstract class nor interface can be instantiated. Abstractclasses can contain both abstract and concrete methods. This means that abstract classes might be a goodchoice in situations where you know how to implement some methods but not others.

Object-Oriented Design Principle: Program to an AbstractionIn order to keep your code flexible and open for extension, you should always try todepend on the highest abstraction in the inheritance hierarchy. You can generally con-sider parent classes or interfaces to be more abstract, and child classes more concrete orspecialized. If you adhere to this principle, the client code will happily accept differentimplementations without needing modification or any type of intervention.

DefinitionAn interpretation of this principle from Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma et al. (Addison-Wesley Professional, 1995) isexpressed as follows: “Program to an abstraction, not an implementation.”

Remember, greater abstraction is always found higher up in the inheritance hierarchyand is often represented through interfaces or abstract classes.

ExampleImagine you have a PurchaseHistory class that holds the list of all the purchases acustomer has carried out. A possible implementation would be like this:

Public Class PurchaseHistory‘...Private purchases As ArrayList

End Class

In this case, PurchaseHistory is dependant on one specific implementation of a list,the System.Collections.ArrayList, and will not accept any other.

However, it is better to make PurchaseHistory dependant on list abstraction, definedby the System.Collections.IList interface:

Public Class PurchaseHistory‘...Private purchases As IList

End Class

This way, not only can ArrayList be used as a purchase list implementation, but anyother class that implements the IList interface. Such a class could be, for example,System.Collections.Generic.List(Of T).

331

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 331

Page 367: Professional Refactoring in Visual Basic

Some important things to remember when choosing an abstract class over an interface are the following:

❑ You can inherit only a single abstract class, and you can implement as many interfaces as youwant. Interfaces are a more flexible choice in this respect.

❑ Abstract classes can contain implemented members, so they are a good choice if you want toprovide or enforce some common behavior for all subclasses.

❑ Abstract classes can declare nonpublic members; interfaces can’t.

❑ If you declare a type as an interface and then add a member, you will force all the implementersto implement the new method and to be recompiled. If you choose an abstract class instead andadd a new method, you can ship a default implementation with the class itself. This way youwill avoid classes that inherit your abstract class being recompiled. This effect can be especiallyrelevant when you are programming frameworks.

❑ Abstract classes, just like any other class, can also implement interfaces. This is actually a com-mon pattern that makes it possible to provide a partial implementation for a certain interface, ifyou can make use of such implementation. If the implementation provided by an abstract classdoes not serve the purpose, you are free to implement an interface and write the class from zero.When an abstract class implements an interface, you are free to implement only members of theinterface you choose. Because the class is abstract, you are free to leave interface members ofyour choice unimplemented.

While abstract classes can contain both abstract and concrete members, there is no reason why you couldn’twrite an abstract class containing purely abstract members. Such an approach is beneficial for frameworkprogrammers, because it frees them to add new members to the abstract class in the next version of the class, as long as they provide default implementation for such members. This way binary compatibility betweenclasses will not be broken. In the case of interfaces, any new member in a new version of an interface meansthat all implementors have to be recompiled in order to implement newly defined members.

GenericityThis is another feature available in Visual Basic since the VB 2005 incarnation that can save you fromwriting a lot of boilerplate code and can also provide additional type safety. Generic types, also knownas parameterized types, enable you to define other types that a class uses at the point of usage, and thatare not in the class itself. This way the same class can work with many types that can be used to para-meterize the class.

The Typed Container ProblemGenerics are easiest to understand in the context of container types. Standard containers from theSystem.Collections namespace provide convenient placeholders for groups of objects and theirmanipulation, such as position or key-based retrieval. Because these containers are general-purpose,they let you add to and retrieve from the container any type of object. After the object is retrieved, ithas to be cast back into the expected type, if Option Strict is activated. (On a side note, in practicewe rarely need to hold objects of different types in the same container.) There are at least two problemswith the general-purpose container scheme:

❑ There is no guarantee that all the objects in the container will be of the type you expect. Thereis nothing to prevent the addition of any object of any type to the container. And if you comeacross an invalid type, a runtime System.InvalidCastException is thrown that objected is

332

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 332

Page 368: Professional Refactoring in Visual Basic

retrieved and cast into the expected type. There should be some way for the compiler to ensurethat only expected types are added to the container.

❑ After you retrieve the type, you have to write the code that casts the object into the expectedtype. Such code is repetitious and, therefore, should not be necessary.

There is a solution, even with standard containers, to the problems just listed. It consists in writing yourown typed container wrappers. Your container wrapper delegates all the work to a standard container ithides from the public. The sample code for this solution is shown in Listing 12-1.

Listing 12-1: Custom-Typed Container Implementation Using Standard Containers

Option Explicit OnOption Strict On

Public Class CustomerListPrivate listValue As IList‘... Other list-like membersPublic Function Add(ByVal customer As Customer) As Integer

‘delegate to internal listMe.listValue.Add(customer)

End FunctionDefault Public Property Item(ByVal index As Integer) As Customer

GetReturn CType(listValue.Item(index), Customer)

End GetSet(ByVal value As Customer)

Me.listValue.Item(index) = valueEnd Set

End PropertyEnd Class

The custom-container approach has two major drawbacks:

❑ It is repetitious and requires a lot of work and discipline. In practice, it is easier not to go to allthe trouble of writing a custom container and simply to use a general-purpose container instead.

❑ Because custom container wrappers do not implement standard non-type-safe container inter-faces (for instance, System.Collections.Ilist), they are not easily interchangeable with othercontainer implementations. For example, it is not easy to replace your CustomerList with astandard ArrayList.

The Generic Container Solution Generics are an elegant solution to the problem of typed containers. They let you specify the type of contained objects at the point of usage and in a single place. After such a container is instantiated, it will receive and return only objects of the specified type, resolving the problem of type safety andcasting code. You can easily define a type-safe container with the Of keyword:

Private listValue As IList(Of Customer)

No more do you need to cast an object to Customer after retrieval or worry about an object of some othertype cropping up from the container. It is worth mentioning that that all container implementations from

333

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 333

Page 369: Professional Refactoring in Visual Basic

the System.Container.Generic namespace also implement interfaces from the standard containernamespace, making them valid replacements for non-generic implementations. For example, System.Container.Generic.List implements the System.Container.IList interface, making it a validreplacement for System.Container.ArrayList as long as the ArrayList is accessed through aSystem.Container.IList interface (see the “Object-Oriented Design Principle: Program to anAbstraction” sidebar earlier in the chapter).

Generics are not container-only. They can be used for many other purposes as well. In VB, .NET, you arefree to write your own generic classes when you need them, and these can be used for various purposes.

Like any other powerful tool, these advanced object-oriented language capabilities that I just talkedabout are useful only if employed well and in the right circumstances. In the hands of an inexperienceduser, these advanced object-oriented features can be completely counterproductive, leading to poorlydesigned software. Therefore, the next step is to take a look at the most common errors that program-mers make when applying these features and how refactoring can help remedy those errors.

Inheritance Abuse and Refactoring Solutions

There are a few things to be aware of when applying inheritance. When you inherit a class, the linkbetween parent and child class (often referred to as superclass and subclass) is established at compilationtime. The link between parent and child class is strong in nature, because all non-private members in theparent are also visible in the child class. This has the following consequences:

❑ The behavior that the child class has inherited from the parent cannot be changed at runtime.

❑ Reusing a child class means also reusing the parent. If there is a need to change some behavior inthe parent, this will most probably provoke a change in a child class, because of tight couplingbetween the two.

You have just seen the arguments that inspired the writers of Design Patterns: Elements of Reusable Object-Oriented Software to coin the “favor object composition over class inheritance” design principle. This issometimes simplistically interpreted as meaning that class inheritance is not necessary at all. Dependingon the circumstances, either composition or inheritance can be beneficial for the design. Nonetheless,inheritance is often abused as a reuse technique because it is so easy to implement.

Object-Oriented Design Principle: Favor Object Composition over Class Inheritance

Class inheritance is a powerful and easily accessible reuse technique. A child class hasimmediate access to functionality provided by a parent class. A child class can overridesome of the parent’s functionality or provide new functionality. Inheritance should beused in cases where the child specializes or extends behavior provided by the parent. It means that there is an “is a kind of” relationship between the two.

334

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 334

Page 370: Professional Refactoring in Visual Basic

Continued

This link between the child and the parent is established at compile time and cannot bechanged at runtime. This link is strong in nature, exposing the child to the inner work-ings of the parent. The consequences are as follows:

❑ Inherited behavior cannot be changed at runtime.

❑ Changes in the parent will most probably provoke a change in all childclasses.

This makes class composition and reuse based on delegation a more flexible and betterchoice when the reused behavior should be changed in runtime. It can be a better choicefor more granular and more encapsulated design. Finally, it is better suited for express-ing a “has a” relationship instead of an “is a kind of” relationship between types.

DefinitionIn Design Patterns: Elements of Reusable Object-Oriented Software, the principle is formu-lated as follows:

“Favor object composition over class inheritance.”

ExampleIn a system that controls a reactor in an atomic plant, you will need to read reactor tem-perature and operate on control rods. Imagine a design in which the AtomicReactorclass inherits the Thermometer implementation:

Public Class BimetalThermometerPublic Function GetCurrentTemperature() As Decimal

‘...End Function

End Class

Public Class AtomicReactorInherits BimetalThermometer

Public Sub ControlTemperature()If GetCurrentTemperature() < _TemperatureLevels.DangerousLow Then

RaiseAlarm()EmergencyReactionIncrease()Exit Sub

ElseIf GetCurrentTemperature() > _TemperatureLevels.DangerousHigh Then

RaiseAlarm()EmergencyReactionDecrease()Exit Sub

ElseIf GetCurrentTemperature() < _TemperatureLevels.Low Then

IncreaseReaction()ElseIf GetCurrentTemperature() > _TemperatureLevels.High Then

335

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 335

Page 371: Professional Refactoring in Visual Basic

DecreaseReaction()End If

End Sub‘...

End Class

In such a design, thermometer functionality is deceptively easy to use. However, theproblems such a design imposes are various:

❑ The reactor is not “a kind of thermometer,” so from a problem domainpoint of view, inheritance is not employed correctly.

❑ The reactor is coupled with a specific implementation of the thermometer.If at one moment, another type of thermometer were used, the applicationwould have to be recompiled.

The relationship between the reactor and thermometer is much better expressed by acomposition relationship between the two:

Public Interface IThermometerFunction GetCurrentTemperature() As Decimal

End Interface

Public Class BimetalThermometerImplements IThermometerPublic Function GetCurrentTemperature() As Decimal _Implements IThermometer.GetCurrentTemperature

‘...End Function

End Class

Public Class InfraredThermometerImplements IThermometerPublic Function GetCurrentTemperature() As Decimal _Implements IThermometer.GetCurrentTemperature

‘...End Function

End Class

Public Class AtomicReactorPrivate thermometerValue As IThermometerPublic Property Thermometer() As IThermometer

GetReturn thermometerValue

End GetSet(ByVal value As IThermometer)

thermometerValue = valueEnd Set

End PropertyPublic Sub ControlTemperature()

If thermometer.GetCurrentTemperature() < _TemperatureLevels.DangerousLow Then

336

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 336

Page 372: Professional Refactoring in Visual Basic

You will now see some typical examples of inheritance misuse. We will start with composition that ismistaken for inheritance.

Composition Mistaken for Inheritance and Other MisusesYou are used to classifying things in your life. You know that a car is a kind of vehicle, that mammals areanimals, that an equilateral triangle is a kind of triangle. However, such relationships between differententities are not simple to establish and have been the subject of scientific and philosophical investigationfrom ancient times. When classifying, you try to establish a relationship between things. You look intocommon characteristics and then group things according to commonality. You rightfully expect anymember of a group to share all the common characteristics of that group.

Continued

Smell: Refused BequestDetecting the SmellA child class that refuses to implement certain parent members is a sign of a refusedbequest. The compiler will oblige a programmer to make a child class seemingly com-pliant with the parent, but a programmer is able to find a way around this obligationimposed by the compiler. The refusal can take a form of an empty overriding subim-plementation, an overriding function that returns a null object — nothing — or a mem-ber that does nothing but raise an exception.

RaiseAlarm()EmergencyReactionIncrease()Exit Sub

ElseIf thermometer.GetCurrentTemperature() > _TemperatureLevels.DangerousHigh Then

RaiseAlarm()EmergencyReactionDecrease()Exit Sub

ElseIf thermometer.GetCurrentTemperature() < _TemperatureLevels.Low Then

IncreaseReaction()ElseIf thermometer.GetCurrentTemperature() > _TemperatureLevels.High Then

DecreaseReaction()End If

End SubEnd Class

Now the reactor “uses” the Thermometer object to obtain temperature readings. The reactor does not depend on a specific implementation of Thermometer, and this implementation can be changed at runtime. Both BimetalThermometer andInfraredThermometer implement the IThermometer interface, and either can beused by the reactor to perform readings.

Finally, this example demonstrates how inheritance and composition are used togetherto make a more flexible and robust design.

337

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 337

Page 373: Professional Refactoring in Visual Basic

Inheritance should be used to express a relationship between more general and more specific cases, oftenexpressed as an “is a” or an “is a kind of” relationship. However, these relationships can often work outdifferently in science, in laymen’s understanding of the world, and, finally, in programming.

In programming, when designing inheritance, you base your thinking on generalization and an “is a”principle. However, everyday classifications do not always translate well into the programming world.When writing code, you have to be strict in the application of principles that govern software design.For example, if you ask any random person what is the most typical bird characteristic, he or she willmost probably reply, “Birds fly.” If you ask this same person what an ostrich is, they will most probablyserenely reply, “An ostrich is a bird.” While classifications you normally use are far from perfect, theygenerally do not interfere with your everyday life. However, similar imperfections in software can havemuch further-reaching consequences.

Refactoring: Replace Inheritance with DelegationMotivationUsing delegation instead of inheritance can often make for much more flexible, moreencapsulated, and more robust design. This is expressed in the “favor object composi-tion over class inheritance” design principle. In this case, delegation refers to havinganother object to do the work, another object to which the work is being delegated.Often the relationship between the two objects is one of composition.

Related RefactoringUse Replace Inheritance with Delegation refactoring to eliminate this smell.

RationaleA child class should support the parent’s interface in its totality. A child that is refusingsome of the parent’s members will result in clients having to know the child, and in theend, this will break encapsulation and polymorphic code. Here is an example of seem-ingly correct inheritance in which the child is refusing some of the parent’s behavior:

Public MustInherit Class Bird‘...Public MustOverride Sub Fly()

End Class

Public Class OstrichInherits Bird‘...Public Overrides Sub Fly()

Throw New InvalidOperationException( _“Ostrich cannot fly, silly!”)

End SubEnd Class

A more benign case of the smell is when the child is not making use of all members thatthe parent provides, but when the child is refusing the parent’s interface, refactoring hasto take place.

338

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 338

Page 374: Professional Refactoring in Visual Basic

Continued

In some cases, the inheritance relationship between two classes is unfounded, becausethe child class refuses some of the parent’s interface, which makes it nonconformant intype. Such a situation should be remedied by refactoring, and often the best solution isto replace inheritance with delegation.

When you program, it is often convenient to start by extending an already existingclass that provides ready behavior that you can reuse. In some cases, you may find thatyou do not want all the behavior exposed through inheritance to be visible. If you needto hide some of the parent’s members, it is best to replace inheritance with delegation.

Related SmellsUse this refactoring to eliminate the Refused Bequest smell and to make design moreflexible by making use of delegation instead of inheritance as a reuse mechanism.

MechanicsThe refactoring is performed in a few sequential steps. You can start by analyzing thesample code used in this refactoring. The PerishableContainer class discards itemsthat have passed in storage more times than is permitted. The class inherits the stan-dard ArrayList and extends its functionality with methods for adding and retrievingperishable items. The problem with the class is that it also exposes all the public mem-bers of ArrayList, making it easy for the client to circumvent time-restricted methods.The solution is to replace inheritance with delegation.

BeforeOption Explicit OnOption Strict On

Public Class PerishableContainerInherits ArrayListPrivate perishIntervalInSecondsValue As IntegerPublic Property PerishIntervalInSeconds() As Integer

GetReturn perishIntervalInSecondsValue

End GetSet(ByVal value As Integer)

perishIntervalInSecondsValue = valueEnd Set

End PropertyPublic Sub LeaveInStorage(ByVal item As PerishableItem)

MyBase.Add(item)End SubPrivate Function TakeOldestFromStorage() As PerishableItem

If MyBase.Count > 0 ThenDim item As PerishableItem = _CType(MyBase.Item(0), PerishableItem)MyBase.Remove(item)If Not HasPerished(item) Then

Return item

339

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 339

Page 375: Professional Refactoring in Visual Basic

End IfEnd IfReturn Nothing

End FunctionPublic Function TakeFromStorage() As PerishableItem

Dim item As PerishableItem = NothingWhile MyBase.Count > 0

item = TakeOldestFromStorage()If item IsNot Nothing Then

Exit WhileEnd If

End WhileReturn item

End FunctionPrivate Function HasPerished( _ByVal item As PerishableItem) As Boolean

If item.CreationTime.AddSeconds( _perishIntervalInSecondsValue) > Date.Now Then

Return FalseEnd IfReturn True

End FunctionEnd Class

Public Interface PerishableItemProperty CreationTime() As Date

End Interface

1. Create a parent type field in the child and initialize it with the object itself(Me):

Public Class PerishableContainerInherits ArrayListPrivate list As ArrayList = Me‘...

End Class

2. Make the code in the child use the newly defined field instead of MyBase:

‘...Private Function TakeOldestFromStorage() As PerishableItem

If list.Count > 0 ThenDim item As PerishableItem = _CType(list.Item(0), PerishableItem)list.Remove(item)If Not HasPerished(item) Then

Return itemEnd If

End IfReturn Nothing

End Function

340

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 340

Page 376: Professional Refactoring in Visual Basic

Continued

3. Remove the “Inherits” part from the class declaration and initialize thefield created in Step 1 with a new instance of the would-be parent:

Public Class PerishableContainerPrivate list As ArrayList = New ArrayList‘...

End Class

4. Expose all members from the parent used by the clients with simple dele-gation code:

Public Class PerishableContainerPublic ReadOnly Property Count() As Integer

GetReturn list.Count

End GetEnd Property‘...

End Class

AfterOption Explicit OnOption Strict On

Public Class PerishableContainerPrivate list As ArrayList = New ArrayListPrivate perishIntervalInSecondsValue As IntegerPublic Property PerishIntervalInSeconds() As Integer

GetReturn perishIntervalInSecondsValue

End GetSet(ByVal value As Integer)

perishIntervalInSecondsValue = valueEnd Set

End PropertyPublic Sub LeaveInStorage(ByVal item As PerishableItem)

list.Add(item)End Sub‘...Private Function TakeOldestFromStorage() As PerishableItem

If list.Count > 0 ThenDim item As PerishableItem = _CType(list.Item(0), PerishableItem)list.Remove(item)If Not HasPerished(item) Then

Return itemEnd If

End IfReturn Nothing

End FunctionPublic Function TakeFromStorage() As PerishableItem

Dim item As PerishableItem = Nothing

341

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 341

Page 377: Professional Refactoring in Visual Basic

It is best if you start inheritance misuse analysis with an example that will demonstrate possible pitfallswith inheritance application. You will then see how the pitfall can be resolved by means of differentrefactorings. The next section starts by looking at a peculiar print-system design.

Refactoring for Inheritance — Print-System IllustrationIn this particular print-system design, shown in Listing 12-2, the central class in the design is an abstractPrintService class. This class declares an abstract PrintJob method that is implemented by differentclasses that inherit PrintSystem. Child classes, like LexmarkX500 and HPLaserJet, implement physicaldevice-specific code that has to take care of communicating with the device and rendering the data in theform that the device understands.

In a system designed like this, each time you need to add another physical device to the group of supporteddevices, you need to add another sibling to the class hierarchy. It is up to the child class to implement alldevice-specific code.

Listing 12-2: Print System Using Inheritance to Incorporate Different Printing Devices

Option Explicit OnOption Strict On

Imports System.IO

Public Enum ServiceState

While list.Count > 0item = TakeOldestFromStorage()If item IsNot Nothing Then

Exit WhileEnd If

End WhileReturn item

End FunctionPrivate Function HasPerished( _ByVal item As PerishableItem) As Boolean

If item.CreationTime.AddSeconds( _perishIntervalInSecondsValue) > Date.Now Then

Return FalseEnd IfReturn True

End FunctionPublic ReadOnly Property Count() As Integer

GetReturn list.Count

End GetEnd Property

End Class

Public Interface PerishableItemProperty CreationTime() As Date

End Interface

342

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 342

Page 378: Professional Refactoring in Visual Basic

Listing 12-2: Print System Using Inheritance to Incorporate Different Printing Devices (continued)

IdleProcessingStopped

End Enum

Public MustInherit Class PrintServicePrivate jobsInQueueValue As IList(Of PrintJob)Private serviceStateValue As ServiceStatePublic Property JobsInQueue() As IList(Of PrintJob)

GetReturn jobsInQueueValue

End GetSet(ByVal value As IList(Of PrintJob))

jobsInQueueValue = valueEnd Set

End PropertyPublic Property ServiceState() As ServiceState

GetReturn serviceStateValue

End GetSet(ByVal value As ServiceState)

serviceStateValue = valueEnd Set

End PropertyPublic Function CreatePrintJob() As PrintJob

Return New PrintJobEnd FunctionPrivate Sub print()

While JobsInQueue.Count > 0PrintJob(JobsInQueue.Item(0))

End WhileEnd SubProtected MustOverride Sub PrintJob(ByVal job As PrintJob)

End Class

Public Class HPLaserJetInherits PrintServicePrivate initializedValue As BooleanPrivate Property Initialized() As Boolean

GetReturn initializedValue

End GetSet(ByVal value As Boolean)

initializedValue = valueEnd Set

End PropertyProtected Overrides Sub PrintJob(ByVal job As PrintJob)

If Not Me.Initialized ThenMe.Initialize()

End IfContinued

343

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 343

Page 379: Professional Refactoring in Visual Basic

Listing 12-2: Print System Using Inheritance to Incorporate Different Printing Devices (continued)

StartDocument()Dim renderedDocument As Stream = RenderDocument(job)WriteDocumentToDevice(renderedDocument)EndDocument()

End SubPrivate Function RenderDocument(ByVal job As PrintJob) As Stream

‘device specific codeEnd SubPrivate Sub WriteDocumentToDevice (ByVal data As Stream)

‘device specific codeEnd SubPrivate Sub Initialize()

‘device specific codeEnd SubPrivate Sub StartDocument()

‘device specific codeEnd SubPrivate Sub EndDocument()

‘device specific codeEnd Sub

End Class

Public Class LexmarkX500Inherits PrintServicePrivate initializedValue As BooleanPrivate Property Initialized() As Boolean

GetReturn initializedValue

End GetSet(ByVal value As Boolean)

initializedValue = valueEnd Set

End PropertyProtected Overrides Sub PrintJob(ByVal job As PrintJob)

If Not Me.Initialized ThenMe.Initialize()

End IfStartDocument()Dim renderedDocument As Stream = RenderDocument(job)WriteDocumentToDevice(renderedDocument)EndDocument()

End SubPrivate Function RenderDocument(ByVal job As PrintJob) As Stream

‘device specific codeEnd SubPrivate Sub WriteDocumentToDevice (ByVal data As Stream)

‘device specific codeEnd SubPrivate Sub Initialize()

‘device specific codeEnd Sub

344

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 344

Page 380: Professional Refactoring in Visual Basic

Listing 12-2: Print System Using Inheritance to Incorporate Different Printing Devices (continued)

Private Sub StartDocument()‘device specific code

End SubPrivate Sub EndDocument()

‘device specific codeEnd Sub

End Class

Public Class PrintJob‘...

End Class

There are a number of problems with this design. Take a look at some of the immediately obvious ones:

❑ Each time a different physical device is connected, the client application has to instantiate a dif-ferent child class. It can still use different device-specific print services in a polymorphic way,thanks to the inheritance relationship between the abstract parent PrintService class and thedevice-specific child classes.

❑ Each time a new physical device has to be supported, a new class has to be added to the hierarchy.The client has to be aware of the class name, and the client code has to be recompiled in order touse the new class (unless a more complex solution is implemented on the client side).

❑ In this design, one physical device corresponds to one printing system. In the future, this mightprove not to be flexible enough. What if you have more than one physical device? You then haveto instantiate a different print service for each device. The inheritance relationship between device-specific and general print functionality makes it complex for the service to support more than onephysical device.

Finally, thinking in problem, domain terms, you can see that the functionality betweenPrintService and the child classes is not particularly related. Child classes do not overrideany of the PrintService methods, except the device-specific PrintJob. However, they adda number of device-specific methods. This can make you wonder if the relationship betweenPrintService and the children is really of an “is a” type.

❑ If you would like one day to reuse device-specific code contained in the LexmarkX500 andHPLaserJet classes, you would be obliged to bring along PrintService code. This is notvery flexible.

From this analysis, it seems that the inheritance relationship between PrintService and the device-specific LexmarkX500 and HPLaserJet classes is not entirely justified. LexmarkX500 and HPLaserJetfit into the picture better as classes that PrintService can delegate to in order to communicate with aphysical device.

Extracting the Device InterfaceYou can start by noting that PrintService has a single abstract method. The abstract PrintJob methodis implemented by both the LexmarkX500 and HPLaserJet classes. This means that PrintService hasno knowledge of concrete child types like LexmarkX500 and HPLaserJet. This is actually a good thing,

345

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 345

Page 381: Professional Refactoring in Visual Basic

so when moving from inheritance to delegation in the design of this print system, you can try to useLexmarkX500 and HPLaserJet in a polymorphic way. In order to do so, you need to extract an IDeviceinterface that will have a single PrintJob method. You can perform interface extraction automaticallywith the help of Refactor! on a single class, but the second class you have to modify manually. First, youhave to make the method PrintJob public and to remove the Inherits keyword from the class declara-tion. The result of interface extraction is shown in Listing 12-3.

Listing 12-3: Extracted IPrintDevice Interface

Public Interface IPrintDeviceSub PrintJob(ByVal job As PrintJob)

End Interface

Public Class HPLaserJetImplements IPrintDevice

Public Sub PrintJob(ByVal job As PrintJob) _Implements IPrintDevice.PrintJob‘device specific codeEnd Sub

‘...End Class

This refactoring makes it possible to use all PrintDevice implementors in a polymorphic way. Thissimplifies things greatly when you are going from an inheritance to a composition relationship betweenthe PrintService and the LexmarkX500 and HPLaserJet classes.

Refactoring: Extract InterfaceMotivationSometimes you might want to expose only a part of the functionality offered by theclass. This is especially true if some clients use only a subset of the functionality pro-vided by the class.

Another case for this refactoring comes when some classes have a part of their interfacein common, but implementation of these members is different. In order for theseclasses to be used in a polymorphic way, they should share a common interface.

Related SmellsUse this refactoring to eliminate the Duplicated Code smell and to provide polymor-phic access to classes.

Mechanics1. Declare the interface with all the members that you wish to expose through

the interface.

2. Make the classes implement the interface by adding the Implements state-ment to both the class and member declarations.

3. Make the clients use the interface and not the concrete classes.

346

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 346

Page 382: Professional Refactoring in Visual Basic

Continued

BeforeOption Explicit OnOption Strict On

Public Class CustomerPrivate firstNameValue As StringPrivate purchaseHistoryValue As PurchaseHistoryPublic Property FirstName() As String

GetReturn firstNameValue

End GetSet(ByVal value As String)

firstNameValue = valueEnd Set

End PropertyPublic Property PurchaseHistory() As PurchaseHistory

GetReturn purchaseHistoryValue

End GetSet(ByVal value As PurchaseHistory)

purchaseHistoryValue = valueEnd Set

End PropertyPublic Function DiscountPercent() As Decimal

If Me.PurchaseHistory.TotalPurchases > 10 ThenReturn 3

ElseIf Me.PurchaseHistory.TotalPurchases > 100 ThenReturn 5 ElseReturn 0

End IfEnd Function

End ClassPublic Interface PerishableItem

Property CreationTime() As DateEnd Interface

AfterOption Explicit OnOption Strict On

Public Interface ICustomerProperty FirstName() As StringProperty PurchaseHistory() As PurchaseHistoryFunction DiscountPercent() As Decimal

End Interface

Public Class CustomerImplements ICustomerPrivate firstNameValue As StringPrivate purchaseHistoryValue As PurchaseHistory

347

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 347

Page 383: Professional Refactoring in Visual Basic

Extract Interface Refactoring in Refactor!Extract Interface refactoring in Refactor! becomes available when you select the class name in the editor.The tool will assume that you wish all the public members of your class to become a part of the newlyextracted interface. If you do not want this, you had better change to private the visibility of the membersyou do not wish to form a part of the extracted interface. After you execute the extraction, you can turnthe visibility back to public.

Refactor! will try to guess the name of the interface based on the originating class. It will add the I prefixto the class name. However, the name can be immediately changed thanks to linked identifiers.

After the extraction is performed, the code is placed inside the same file as the originating class. If you wishto move the interface to a separate file, as is most probably advisable, you can use the Move Type to Filerefactoring that Refactor! makes available. You can see Refactor! ready to extract the interface in Figure 12-1.

Delegation Instead of Inheritance Inside the Print SystemNow you are ready to modify PrintService so it delegates to IDevice instead on relying on a subclass to implement device-specific behavior. PrintService is no longer abstract, so the MustInherit keywordcan be removed from the declaration. The abstract PrintJob method now delegates to a device, and itshould be declared private. The code for PrintService after it has been refactored to use delegationinstead of inheritance is visible in Listing 12-4.

Public Property FirstName() As String _Implements ICustomer.FirstName

GetReturn firstNameValue

End GetSet(ByVal value As String)

firstNameValue = valueEnd Set

End PropertyPublic Property PurchaseHistory() As PurchaseHistory _Implements ICustomer.PurchaseHistory

GetReturn purchaseHistoryValue

End GetSet(ByVal value As PurchaseHistory)

purchaseHistoryValue = valueEnd Set

End PropertyPublic Function DiscountPercent() As Decimal _Implements ICustomer.DiscountPercent

If Me.PurchaseHistory.TotalPurchases > 10 ThenReturn 3

ElseIf Me.PurchaseHistory.TotalPurchases > 100 ThenReturn 5

ElseReturn 0

End IfEnd Function

End Class

348

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 348

Page 384: Professional Refactoring in Visual Basic

Figure 12-1

Listing 12-4: Delegation Instead of Inheritance for PrintService

Public Class PrintServicePrivate device As IPrintDevicePrivate jobsInQueueValue As IList(Of PrintJob)Private serviceStateValue As ServiceState

Public Sub New()Select Case My.Settings(“DeviceName”).ToString

Case “HPLaserJet”device = New HPLaserJet

Case “LexmarkX500”device = New LexmarkX500

End SelectEnd SubPublic Property Device1() As IPrintDevice

GetReturn device

End GetSet(ByVal value As IPrintDevice)

device = valueEnd Set

End PropertyPublic Property JobsInQueue() As IList(Of PrintJob)

GetReturn jobsInQueueValue

End GetSet(ByVal value As IList(Of PrintJob))

jobsInQueueValue = valueEnd Set

End PropertyPublic Property ServiceState() As ServiceState

GetReturn serviceStateValue

End GetSet(ByVal value As ServiceState)

Continued

349

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 349

Page 385: Professional Refactoring in Visual Basic

Listing 12-4: Delegation Instead of Inheritance for PrintService (continued)

serviceStateValue = valueEnd Set

End PropertyPublic Function CreatePrintJob() As PrintJob

Return New PrintJobEnd FunctionPrivate Sub print()

While JobsInQueue.Count > 0PrintJob(JobsInQueue.Item(0))

End WhileEnd SubPrivate Sub PrintJob(ByVal job As PrintJob)

Me.device.PrintJob(job)End Sub

End Class

Eliminating Duplication by Means of InheritanceIf you go back to Listing 12-2 for the moment and compare the implementation of the PrintJob method in LexmarkX500 and HPLaserJet, you can see that these methods are identical. The same goes for theproperty Initialized. One way that classes can share implementation is by means of inheritance. Thesolution in this case is to extract the superclass that both LexmarkX500 and HPLaserJet could inherit. Youcan name this class PrintDevice. Since the PrintJob method is calling other device-specific methods,you need to declare those methods in PrintDevice as well. However, because implementations of thesemethods differ from device to device, they have to be declared abstract, meaning that the extracted super-class has to be abstract as well. PrintDevice has to implement the IPrintDevice interface. You can seethe result of superclass extraction in Listing 12-5.

Listing 12-5: Extracted PrintDevice Abstract Superclass

Option Explicit OnOption Strict On

Imports System.IO

Public MustInherit Class PrintDeviceImplements IPrintDevicePublic Sub PrintJob(ByVal job As PrintJob) _Implements IPrintDevice.PrintJob

If Not Me.Initialized ThenMe.Initialize()

End IfStartDocument()Dim renderedDocument As Stream = RenderDocument(job)WriteDocumentToDevice(renderedDocument)EndDocument()

End SubProtected MustOverride Function RenderDocument( _ByVal job As PrintJob) As StreamProtected MustOverride Sub WriteDocumentToDevice( _ByVal data As Stream)

350

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 350

Page 386: Professional Refactoring in Visual Basic

Listing 12-5: Extracted PrintDevice Abstract Superclass (continued)

Protected MustOverride Sub Initialize()Protected MustOverride Sub StartDocument()Protected MustOverride Sub EndDocument()Public MustOverride Property Initialized() As Boolean

End Class

Public Class LexmarkX500Inherits PrintDevicePrivate initializedValue As BooleanPublic Overrides Property Initialized() As Boolean

GetReturn initializedValue

End GetSet(ByVal value As Boolean)

initializedValue = valueEnd Set

End PropertyProtected Overrides Function RenderDocument( _ByVal job As PrintJob) As Stream

‘device specific codeEnd FunctionProtected Overrides Sub WriteDocumentToDevice( _ByVal data As Stream)

‘device specific codeEnd SubProtected Overrides Sub Initialize()

‘device specific codeEnd SubProtected Overrides Sub StartDocument()

‘device specific codeEnd SubProtected Overrides Sub EndDocument()

‘device specific codeEnd Sub

End Class

Public Class HPLaserJetInherits PrintDevicePrivate initializedValue As BooleanPublic Overrides Property Initialized() As Boolean

GetReturn initializedValue

End GetSet(ByVal value As Boolean)

initializedValue = valueEnd Set

End PropertyProtected Overrides Function RenderDocument( _ByVal job As PrintJob) As Stream

‘device specific codeEnd FunctionProtected Overrides Sub WriteDocumentToDevice( _

Continued

351

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 351

Page 387: Professional Refactoring in Visual Basic

Listing 12-5: Extracted PrintDevice Abstract Superclass (continued)

ByVal data As Stream)‘device specific code

End SubProtected Overrides Sub Initialize()

‘device specific codeEnd SubProtected Overrides Sub StartDocument()

‘device specific codeEnd SubProtected Overrides Sub EndDocument()

‘device specific codeEnd Sub

End Class

Refactoring: Extract SuperclassMotivationOne way that different classes can share implementation is through inheritance.However, you cannot always identify duplicated implementations before you actuallycode the classes. Sometimes this duplication can be the result of newly added featuresor some other refactoring. Whatever the origin of duplication, you can eliminate it byextracting a superclass.

Sometimes the classes share implementation for some members and only declarationfor others. In those cases, the extracted class should be abstract, because that way itwill permit both abstract and concrete methods. If classes share only an interface, thenyou should use Extract Interface refactoring instead.

This refactoring is an alternative to Extract Class refactoring that promotes reuse by meansof delegation. In case you later on decide that delegation was a better choice, you canapply Replace Inheritance with Delegation refactoring in order to reverse the situation.

Related SmellsUse this refactoring to eliminate the Duplicated Code and Large Class smells.

Mechanics1. Create an empty abstract superclass and make the targeted class inherit

the superclass.

2. Start moving class members up to the superclass one by one. If only thedeclaration is common between future subclasses, pull only the declarationto the superclass and make the member abstract. If member implementa-tion is common as well, pull up the complete member.

3. Execute tests after each pull.

4. If one of the subclasses ends up having no members of its own because allhave been pulled up to superclass, you can eliminate it altogether.

5. Inspect the clients. If all they now use are superclass members, change thedeclared type to superclass.

352

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 352

Page 388: Professional Refactoring in Visual Basic

Continued

BeforePublic Class OrdinaryCustomer

Private firstNameValue As StringPrivate basketValue As ShoppingBasketPublic Property FirstName() As String

GetReturn firstNameValue

End GetSet(ByVal value As String)

firstNameValue = valueEnd Set

End PropertyPublic Property Basket() As ShoppingBasket

GetReturn basketValue

End GetSet(ByVal value As ShoppingBasket)

basketValue = valueEnd Set

End PropertyPublic Function CalculateDiscount() As Decimal

If Basket.Total > 3000 ThenReturn 2

End IfReturn 0

End FunctionEnd Class

Public Class RegisteredCustomerPrivate firstNameValue As StringPrivate historyValue As ShoppingHistoryPrivate basketValue As ShoppingBasketPublic Property FirstName() As String

GetReturn firstNameValue

End GetSet(ByVal value As String)

firstNameValue = valueEnd Set

End Property

Public Property Basket() As ShoppingBasketGet

Return basketValueEnd GetSet(ByVal value As ShoppingBasket)

basketValue = valueEnd Set

End PropertyPublic Function CalculateDiscount() As Decimal

If History.Total > 3000 ThenReturn 3

353

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 353

Page 389: Professional Refactoring in Visual Basic

ElseIf History.Total > 10000 ThenReturn 5

End IfReturn 0.5

End FunctionPublic Property History() As ShoppingHistory

GetReturn historyValue

End GetSet(ByVal value As ShoppingHistory)

historyValue = valueEnd Set

End PropertyEnd Class

AfterPublic Class Customer

Private firstNameValue As StringPrivate basketValue As ShoppingBasketPublic Property FirstName() As String

GetReturn firstNameValue

End GetSet(ByVal value As String)

firstNameValue = valueEnd Set

End PropertyPublic Property Basket() As ShoppingBasket

GetReturn basketValue

End GetSet(ByVal value As ShoppingBasket)

basketValue = valueEnd Set

End PropertyPublic MustOverride Function CalculateDiscount() As Decimal

End Class

Public Class OrdinaryCustomerInherits CustomerPublic Overrides Function CalculateDiscount() As Decimal

If Basket.Total > 3000 ThenReturn 2

End IfReturn 0

End FunctionEnd Class

Public Class RegisteredCustomerInherits CustomerPrivate historyValue As ShoppingHistoryPublic Property History() As ShoppingHistory

354

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 354

Page 390: Professional Refactoring in Visual Basic

You have now eliminated some code duplication that occurred between LexmarkX500 and HPLaserJet.As it happens, though, not all the code duplicated between the two classes has been identified. You need torefactor these classes further. You can see what you can do about leftover duplication in the next section.

Eliminating Duplication by Pulling up MembersVery often, code duplicated between the sibling classes can surface as a result of other refactorings, likemethod extraction and method parameterization. Another source of duplication is indiscriminate featureexpansion, when a member is first added to one class and later on added to another sibling without thecode in other siblings being checked.

The solution in cases wherein code is duplicated between all the siblings is to pull the member up to aparent class. This way, the duplication is eliminated and common features maintained in the parent class.

If you compare, LexmarkX500 and HPLaserJet classes from the print-system example, you will see that theproperty Initialized is identical in both classes. The solution is to pull up the Initialized property tothe PrintDevice class. The resulting code is shown in Listing 12-6.

Listing 12-6: Pulling up Initialized Property Common to LexmarkX500 and HPLaserJet

Option Explicit OnOption Strict On

Imports System.IO

Public MustInherit Class PrintDeviceImplements IPrintDevicePrivate initializedValue As BooleanPublic Property Initialized() As Boolean

GetReturn initializedValue

End GetSet(ByVal value As Boolean)

Continued

GetReturn historyValue

End GetSet(ByVal value As ShoppingHistory)

historyValue = valueEnd Set

End PropertyPublic Overrides Function CalculateDiscount() As Decimal

If History.Total > 3000 ThenReturn 3

ElseIf History.Total > 10000 ThenReturn 5

End IfReturn 0.5

End FunctionEnd Class

355

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 355

Page 391: Professional Refactoring in Visual Basic

Listing 12-6: Pulling up Initialized Property Common to LexmarkX500 and HPLaserJet (continued)

initializedValue = valueEnd Set

End PropertyPublic Sub PrintJob(ByVal job As PrintJob) _Implements IPrintDevice.PrintJob

If Not Me.Initialized ThenMe.Initialize()

End IfStartDocument()Dim renderedDocument As Stream = RenderDocument(job)WriteDocumentToDevice(renderedDocument)EndDocument()

End SubProtected MustOverride Function RenderDocument( _ByVal job As PrintJob) As StreamProtected MustOverride Sub WriteDocumentToDevice( _ByVal data As Stream)Protected MustOverride Sub Initialize()Protected MustOverride Sub StartDocument()Protected MustOverride Sub EndDocument()

End Class

Public Class LexmarkX500Inherits PrintDeviceProtected Overrides Function RenderDocument( _ByVal job As PrintJob) As Stream

‘device specific codeEnd FunctionProtected Overrides Sub WriteDocumentToDevice( _ByVal data As Stream)

‘device specific codeEnd SubProtected Overrides Sub Initialize()

‘device specific codeEnd SubProtected Overrides Sub StartDocument()

‘device specific codeEnd SubProtected Overrides Sub EndDocument()

‘device specific codeEnd Sub

End Class

Public Class HPLaserJetInherits PrintDeviceProtected Overrides Function RenderDocument( _ByVal job As PrintJob) As Stream

‘device specific codeEnd FunctionProtected Overrides Sub WriteDocumentToDevice( _

356

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 356

Page 392: Professional Refactoring in Visual Basic

Listing 12-6: Pulling up Initialized Property Common to LexmarkX500 and HPLaserJet (continued)

ByVal data As Stream)‘device specific code

End SubProtected Overrides Sub Initialize()

‘device specific codeEnd SubProtected Overrides Sub StartDocument()

‘device specific codeEnd SubProtected Overrides Sub EndDocument()

‘device specific codeEnd Sub

End Class

This concludes the redesign of the print system. In the first stage, inheritance was replaced with delegation,only to be introduced again later on. First, interface inheritance was introduced to provide polymorphicaccess to print devices, and then class inheritance to reduce duplication between different devices.

Such gradual changes to code are not uncommon, because some smells become visible only after moreimmediate ones are eliminated. The optimal design is hardly a tangible set of classes and their relations;it is a continuously changing set of decisions that corresponds best to current circumstances.

Continued

Refactoring: Pull up MethodMotivationA way to share implementation between classes is to have a common ancestor. Allmembers common to all the siblings should be contained in the parent class, in order to avoid unnecessary duplication.

When some member is completely identical in all of the child classes, in order toavoid duplication, the member should be moved from the child classes to a parentclass or interface.

Related SmellsUse this refactoring to eliminate the Duplicated Code and Large Class smells.

MechanicsThe simplest case in which to use this refactoring is when the methods in child classesare completely identical. Sometimes, however, this refactoring should be preceded byother refactorings, such as method extraction and method parameterization, until themethods are made equal.

The mechanics are similar but simpler when you are pulling up abstract method decla-rations to a parent abstract class or interface.

1. Cut and paste the method from child to base class.

357

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 357

Page 393: Professional Refactoring in Visual Basic

2. If the recently moved method is using some other methods in the childclasses, declare the method as abstract in the parent and make the childclasses override it.

If the method is using property available only in child classes, considermoving the property up as well.

3. Delete the method from rest of the siblings.

4. Compile and test.

BeforePublic Class Customer

Private firstNameValue As StringPrivate basketValue As ShoppingBasketPublic Property FirstName() As String

GetReturn firstNameValue

End GetSet(ByVal value As String)

firstNameValue = valueEnd Set

End PropertyPublic Property Basket() As ShoppingBasket

GetReturn basketValue

End GetSet(ByVal value As ShoppingBasket)

basketValue = valueEnd Set

End PropertyPublic MustOverride Function CalculateDiscount() As Decimal

End Class

Public Class OrdinaryCustomerInherits CustomerPublic Overrides Function CalculateDiscount() As Decimal

If Basket.Total > 3000 ThenReturn 2

End IfReturn 0

End FunctionPublic Sub SaveShoppingBasket()

shoppingData.Save(Me, Me.Basket)End Sub

End Class

Public Class RegisteredCustomerInherits CustomerPrivate historyValue As ShoppingHistoryPublic Property History() As ShoppingHistory

Get

358

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 358

Page 394: Professional Refactoring in Visual Basic

Continued

Return historyValueEnd GetSet(ByVal value As ShoppingHistory)

historyValue = valueEnd Set

End PropertyPublic Overrides Function CalculateDiscount() As Decimal

If History.Total > 3000 ThenReturn 3

ElseIf History.Total > 10000 ThenReturn 5

End IfReturn 0.5

End FunctionPublic Sub SaveShoppingBasket()

shoppingData.Save(Me, Me.Basket)End Sub

End Class

AfterPublic Class Customer

Private firstNameValue As StringPrivate basketValue As ShoppingBasketPublic Property FirstName() As String

GetReturn firstNameValue

End GetSet(ByVal value As String)

firstNameValue = valueEnd Set

End PropertyPublic Property Basket() As ShoppingBasket

GetReturn basketValue

End GetSet(ByVal value As ShoppingBasket)

basketValue = valueEnd Set

End PropertyPublic MustOverride Function CalculateDiscount() As DecimalPublic Sub SaveShoppingBasket()

shoppingData.Save(Me, Me.Basket)End Sub

End Class

Public Class OrdinaryCustomerInherits CustomerPublic Overrides Function CalculateDiscount() As Decimal

If Basket.Total > 3000 ThenReturn 2

End If

359

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 359

Page 395: Professional Refactoring in Visual Basic

Making Use of GenericsGenerics add additional type safety to your code and liberate you from writing tedious typecastingcode. Existing classes can often be further enhanced by the means of generics, which permit parametertype to be declared at the point of usage.

Very often, a good signal that generics can be employed is that the Object type is used in declarations.An additional signal of the possibility for upgrade to generic types is the use of container types from the System.Collections namespace. Generic containers are placed inside the System.Collections.Generic namespace and have close correspondence with containers from theSystem.Collections namespace.

In practice, you rarely operate in the same way on objects that are different in type. You can operate onobjects of different types, but through polymorphic mechanisms, meaning that those objects belong tothe same supertype. Even in those cases, a class can be made generic, and a parameter type can be a common supertype.

When introducing generics, you should make sure that all uses of Object correspond to the same type.Generic types can be parameterized with more than one parameter type, so for each implicit type, a newparameter type should be used.

Return 0End FunctionEnd Class

Public Class RegisteredCustomerInherits CustomerPrivate historyValue As ShoppingHistoryPublic Property History() As ShoppingHistory

GetReturn historyValue

End GetSet(ByVal value As ShoppingHistory)

historyValue = valueEnd Set

End PropertyPublic Overrides Function CalculateDiscount() As Decimal

If History.Total > 3000 ThenReturn 3

ElseIf History.Total > 10000 ThenReturn 5

End IfReturn 0.5

End FunctionEnd Class

360

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 360

Page 396: Professional Refactoring in Visual Basic

Continued

Refactoring: Replace General-Purpose Reference with Parameter Type

MotivationBefore generics were introduced to VB, the only way you could write code that dealtwith objects of any type was to declare a reference as an Object. Sometimes it is usefulto limit the type of manipulated objects at the point of usage for the following reasons:

❑ Additional type safety is provided.

❑ Duplicate typecasting code can be eliminated.

Related SmellsUse this refactoring to eliminate the duplicated code and to turn dynamically typedcode into strongly statically typed explicit code.

MechanicsGeneral-purpose reference can often be found when the type Object is employed asthe declaring type.

1. Identify how many different underlying types are declared as Object. Forexample, IList interface references only one type, one that is added andretrieved from the list, while IDictionary references two: one is used as akey, and the other is stored in the dictionary.

2. For each different underlying type discovered in Step 1, add one typeparameter in the class declaration.

3. Replace Object with the parameter type declared in Step 2.

BeforeOption Explicit OnOption Strict On

Public Class PerishableContainerPrivate list As ArrayList = New ArrayListPrivate perishIntervalInSecondsValue As IntegerPrivate timeInStorage As IDictionary = New Hashtable

Public Property PerishIntervalInSeconds() As IntegerGet

Return perishIntervalInSecondsValueEnd GetSet(ByVal value As Integer)

perishIntervalInSecondsValue = valueEnd Set

End PropertyPublic Sub LeaveInStorage(ByVal item As Object)

list.Add(item)timeInStorage.Add(item, DateAndTime.TimeOfDay)

End Sub‘...

361

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 361

Page 397: Professional Refactoring in Visual Basic

Private Function TakeOldestFromStorage() As ObjectIf list.Count > 0 Then

Dim item As Object = _CType(list.Item(0), Object)list.Remove(item)If Not HasPerished(item) Then

Return itemEnd If

End IfReturn Nothing

End FunctionPublic Function TakeFromStorage() As Object

Dim item As Object = NothingWhile list.Count > 0

item = TakeOldestFromStorage()If item IsNot Nothing Then

Exit WhileEnd If

End WhileReturn item

End FunctionPrivate Function HasPerished( _ByVal item As Object) As Boolean

If CType(timeInStorage.Item(item), DateTime).AddSeconds(_

perishIntervalInSecondsValue) > Date.Now ThenReturn False

End IfReturn True

End FunctionPublic ReadOnly Property Count() As Integer

GetReturn list.Count

End GetEnd Property

End Class

AfterOption Explicit OnOption Strict On

Imports System.Collections.Generic

Public Class PerishableContainer(Of Perishable)Private list As IList(Of Perishable) = New List(Of

Perishable)Private perishIntervalInSecondsValue As IntegerPrivate timeInStorage As IDictionary(Of Perishable, DateTime)

= _

362

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 362

Page 398: Professional Refactoring in Visual Basic

New Dictionary(Of Perishable, DateTime)

Public Property PerishIntervalInSeconds() As IntegerGet

Return perishIntervalInSecondsValueEnd GetSet(ByVal value As Integer)

perishIntervalInSecondsValue = valueEnd Set

End PropertyPublic Sub LeaveInStorage(ByVal item As Perishable)

list.Add(item)timeInStorage.Add(item, DateAndTime.TimeOfDay)

End Sub‘...Private Function TakeOldestFromStorage() As Perishable

If list.Count > 0 ThenDim item As Perishable = list.Item(0)list.Remove(item)If Not HasPerished(item) Then

Return itemEnd If

End IfReturn Nothing

End FunctionPublic Function TakeFromStorage() As Perishable

Dim item As Perishable = NothingWhile list.Count > 0

item = TakeOldestFromStorage()If item IsNot Nothing Then

Exit WhileEnd If

End WhileReturn item

End FunctionPrivate Function HasPerished( _ByVal item As Perishable) As Boolean

If CType(timeInStorage.Item(item), DateTime).AddSeconds(_

perishIntervalInSecondsValue) > Date.Now ThenReturn False

End IfReturn True

End FunctionPublic ReadOnly Property Count() As Integer

GetReturn list.Count

End GetEnd Property

End Class

363

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 363

Page 399: Professional Refactoring in Visual Basic

Inheritance and Generic Types in the Rent-a-Wheels Application

You have probably noticed by now in the Rent-a-Wheels application that various Data classes (BranchData,ModelData, and so on) have a number of identically declared and implemented methods. You have alsonoticed that these classes are used in a similar way, except for the domain objects they work upon. The pres-ence of identical methods means that code duplication is going on between the classes. From this, a few con-clusions can be reached:

❑ Data classes share some of their implementation, mainly private methods like AddParameteror ExecuteNonQuery.

❑ Interfaces are almost identical, except for the single object the classes operate on. For example, subDelete is in BranchData declared as Public Sub Delete(ByVal branch As Branch), whilein ModelData, the method is declared as Public Sub Delete(ByVal model As Model).

❑ Duplication can be avoided by superclass extraction.

Extracting SuperBecause some of the methods between different Data classes differ in implementation, the superclasshas to be abstract. You can call it AbstractData, and you can move all identical methods to it. Becausethe majority of these methods are private, visibility has to be changed to Protected so that subclassescan make use of these methods. These methods are AddParameter, ExecuteNonQuery, FillDataset,and so on.

After this extraction is performed, it becomes obvious that some of the methods, such asPrepareDataObjects, are used only by methods that found their way to the superclass, meaning thatthese methods can stay private.

Employing Generics Declaration of public methods differs only in the type of domain object upon which the Data classmanipulates. You can make AbstractData a parameterized class and let the domain object be definedat the point of usage. So AbstractData is declared as a generic class.

Public MustInherit Class AbstractData(Of PersistedObject)‘...

Public MustOverride Sub Delete(ByVal persisted As PersistedObject)End Class

The public methods now receive PersistedObject and can be overridden and implemented in each ofthe child data classes.

It is actually the child Data (BranchData, ModelData and so on) that parameterizes the abstract super-class AbstractData and defines the parameter type as Branch, Model, and so on. So BranchData isdeclared as follows:

Public Class BranchDataInherits AbstractData(Of Branch)

364

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 364

Page 400: Professional Refactoring in Visual Basic

‘...Public Overrides Sub Delete(ByVal branch As Branch)

Dim command As IDbCommand = New SqlCommandAddParameter(command, IdParamterName, DbType.Int32, _branch.Id)ExecuteNonQuery(command, DeleteBranchSql)

End Sub

This way, AbstractData can expose a common interface in the form of public methods like Deleteand Update that use a parameterized type. Only the parameterized GetAll method provokes change inform classes, because it uses a parameterized list to return a list of all domain objects. The declaration ofthe GetAll method in BranchData looks like this:

Public Overrides Function GetAll() As IList(Of Branch)

It seems that the Data class can be a very useful and reusable class that can serve as a base of futureconcrete Data class implementations. With this, you have managed to organize the Data classes into ahierarchy that both reduces the duplication and makes it possible for Data classes to be used in a poly-morphic manner.

You may still be concerned with one thing, however. AbstractData depends on a concrete ADO.NET provider, meaning that it would not be easy to change the system so that it uses a differentdatabase motor.

Extract Data Objects Provider Class To make AbstractData database-neutral, you can extract the data provider object-creation code to sep-arate the AbstractDataObjectsProvider class:

Option Explicit OnOption Strict On

Public MustInherit Class AbstractDataObjectsProviderPublic MustOverride Function InstantiateAdapter() _As System.Data.IDbDataAdapterPublic MustOverride Function InstantiateConnection() _As System.Data.IDbConnection

End Class

AbstractData can hold a field of AbstractDataObjectsProvider and delegate data-object creationto this class. You can also make the child classes supply the concrete DataObjectsProvider through aconstructor method, together with a connection string.

The BranchData constructor now looks like this:

Public Sub New()MyBase.New(BranchData.MSSQLConnectionString, _New MSSQLDataObjectsProvider)

End Sub

365

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 365

Page 401: Professional Refactoring in Visual Basic

The MsSqlDataObjectsProvider code contains simple creation code:

Option Strict OnOption Explicit On

Imports System.Data.SqlClient

Public Class MsSqlDataObjectsProviderInherits AbstractDataObjectsProviderPublic Overrides Function InstantiateAdapter() _As System.Data.IDbDataAdapter

Return New SqlDataAdapterEnd FunctionPublic Overrides Function InstantiateConnection() _As System.Data.IDbConnection

Return New SqlConnectionEnd Function

End Class

With this you have moved the dependency on a certain data provider down in the hierarchy. What iseven more important is that provider creation code is concentrated on a single line, so all it takes tochange the provider is to change a single line in the concrete data classes and to implement anotherDataObjectProvider. You can see the resulting design in Figure 12-2.

Figure 12-2

Now take a look at the most important new code for Rent-a-Wheels that resulted from the refactoringsdescribed in this chapter. It is shown in Listing 12-7.

Listing 12-7: Illustrative Rent-a-Wheels Classes After Advanced Refactorings

Option Strict OnOption Explicit On

Imports System.Data

Public MustInherit Class AbstractData(Of PersistedObject)Private connectionStringValue As StringPrivate providerValue As AbstractDataObjectsProviderPublic Sub New(ByVal connectionString As String, _ByVal provider As AbstractDataObjectsProvider)

Me.ConnectionString = connectionStringMe.Provider = provider

BranchData ModelData VehicleCategoryData VehicleData MsSqlDataObjectsProvider

AbstractData

PersistedObject AbstractDataObjectsProvider

366

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 366

Page 402: Professional Refactoring in Visual Basic

Listing 12-7: Illustrative Rent-a-Wheels Classes After Advanced Refactorings (continued)

End SubPublic Property ConnectionString() As String

GetReturn connectionStringValue

End GetSet(ByVal value As String)

connectionStringValue = valueEnd Set

End PropertyPublic Property Provider() As AbstractDataObjectsProvider

GetReturn providerValue

End GetSet(ByVal value As AbstractDataObjectsProvider)

providerValue = valueEnd Set

End PropertyPrivate Function CreateConnection() As IDbConnection

Dim connection As IDbConnection = Provider.InstantiateConnection()connection.ConnectionString = Me.ConnectionStringReturn connection

End FunctionPrivate Function PrepareDataObjects(ByVal command As IDbCommand, _ByVal sql As String) As IDbConnection

Dim connection As IDbConnection = CreateConnection()connection.Open()command.Connection = connectioncommand.CommandText = sqlReturn connection

End FunctionProtected Sub AddParameter(ByVal command As IDbCommand, _ByVal parameterName As String, ByVal parameterType As DbType, _ByVal paramaterValue As Object)

Dim parameter As IDbDataParameter = command.CreateParameter()parameter.ParameterName = parameterNameparameter.DbType = parameterTypeparameter.Value = paramaterValuecommand.Parameters.Add(parameter)

End SubProtected Function FillDataset(ByVal command As IDbCommand, _ByVal sql As String) As DataSet

Dim connection As IDbConnection = PrepareDataObjects(command, sql)Dim adapter As IDbDataAdapter = Provider.InstantiateAdapter()Dim dataSet As New DataSetadapter.SelectCommand = commandadapter.Fill(dataSet)connection.Close()Return dataSet

End FunctionProtected Sub ExecuteNonQuery(ByVal command As IDbCommand, _ByVal sql As String)

Continued

367

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 367

Page 403: Professional Refactoring in Visual Basic

Listing 12-7: Illustrative Rent-a-Wheels Classes After Advanced Refactorings (continued)

Dim connection As IDbConnection = PrepareDataObjects(command, sql)command.ExecuteNonQuery()connection.Close()

End SubPublic MustOverride Function GetAll() As IList(Of PersistedObject)Public MustOverride Sub Delete(ByVal persisted As PersistedObject)Public MustOverride Sub Update(ByVal persisted As PersistedObject)Public MustOverride Sub Insert(ByVal persisted As PersistedObject)

End Class

Imports System.DataImports System.Data.SqlClientImports RentAWheel.Data.ColumnNames

Public Class BranchDataInherits AbstractData(Of Branch)

Private Const MSSQLConnectionString As String = _“Data Source=LOCALHOST;Initial Catalog=RENTAWHEELS;” + _“User ID=RENTAWHEELS_LOGIN;Password=RENTAWHEELS_PASSWORD_123”

Private Const SelectAllFromBranchSql As String = _“Select * from Branch”Private Const DeleteBranchSql As String = _“Delete Branch Where BranchId = @Id”Private Const InsertBranchSql As String = _“Insert Into Branch (BranchName) Values(@Name)“Private Const UpdateBranchSql As String = _“Update Branch Set BranchName = @Name Where BranchId = @Id”

Private Const IdParamterName As String = “@Id”Private Const NameParameterName As String = “@Name”

Public Sub New()MyBase.New(BranchData.MSSQLConnectionString, _New MsSqlDataObjectsProvider)

End Sub

Public Overrides Sub Delete(ByVal branch As Branch)Dim command As IDbCommand = New SqlCommandAddParameter(command, IdParamterName, DbType.Int32, _branch.Id)ExecuteNonQuery(command, DeleteBranchSql)

End SubPublic Overrides Sub Insert(ByVal branch As Branch)

Dim command As IDbCommand = New SqlCommandAddParameter(command, NameParameterName, DbType.String, _branch.Name)ExecuteNonQuery(command, InsertBranchSql)

End SubPublic Overrides Sub Update(ByVal branch As Branch)

Dim command As IDbCommand = New SqlCommand

368

Part IV: Advanced Refactorings

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 368

Page 404: Professional Refactoring in Visual Basic

Listing 12-7: Illustrative Rent-a-Wheels Classes After Advanced Refactorings (continued)

AddParameter(command, NameParameterName, DbType.String, _branch.Name)AddParameter(command, IdParamterName, DbType.Int32, _branch.Id)ExecuteNonQuery(command, UpdateBranchSql)

End SubPublic Overrides Function GetAll() As IList(Of Branch)

Dim command As IDbCommand = New SqlCommandDim branchesSet As DataSet = FillDataset(command, _SelectAllFromBranchSql)Dim table As DataTable = branchesSet.Tables(0)Dim branches As IList(Of Branch) = New List(Of Branch)For Each row As DataRow In table.Rows

branches.Add(New Branch(CInt(row.Item(BranchTable.Id)), _row.Item(BranchTable.Name).ToString))

NextReturn branches

End FunctionEnd Class

As was the case with other chapters, the complete code is available for download at the book’s website onwww.wrox.com, and I recommend you analyze it in more depth. The refactorings performed on Rent-a-Wheels in this chapter were quite complex and have significantly changed the application’s internals.

SummaryThis chapter dealt with some advanced and powerful object-oriented concepts. You have witnessed thepower and dangers of inheritance. With great power comes great responsibility. So you have to be care-ful not to misuse inheritance, or your losses will soon outweigh the benefits.

Even when inheritance is ill applied, there is still a solution. You can refactor your code so it uses delega-tion instead of inheritance. And in cases where inheritance is well applied, it still might need a bit oftweaking. You can do this by moving members up and down the hierarchy.

In cases where you can see that you could benefit from inheritance, you can perform Extract Interfaceand Extract Superclass refactoring. By extracting an interface, you open the door for polymorphic mech-anisms in your code, and by extracting a superclass, you reduce code duplication between the siblings.

In the Rent-a-Wheels example, you have seen these advanced refactorings in practice and how, whencombined, they can provide a solution to a number of problems, increasing the flexibility and reusevalue of your code.

The next chapter will wrap up the story on inheritance. I will also talk about large-scale code organiza-tion and give some insight into the concepts of namespace, assembly, and related refactorings.

369

Chapter 12: Advanced Object-Oriented Concepts

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 369

Page 405: Professional Refactoring in Visual Basic

79796c12.qxd:WroxPro 2/23/08 8:39 AM Page 370

Page 406: Professional Refactoring in Visual Basic

Code Organization on a Large Scale

Until now, I have hardly mentioned Visual Basic’s large-scale organizational mechanisms like name-spaces and assemblies. The organization of your project and the partitioning of types to namespacesand assemblies can have a profound effect on the maintainability of your project and the develop-ment process in general.

Many of these issues are not apparent with small projects. They become much more relevant asprojects grow and the number of types reaches hundreds or even thousands. Dependencies, on theother hand, are notorious to multiply with the age of the project.

This does not mean that these issues should be taken lightly with smaller projects. Projects have a tendency to grow and decisions that you make early on can result in important consequencesmuch later.

In this chapter, you will:

❑ See criteria that can be applied when using namespaces and assemblies to organize your project.

❑ Read about dependencies and see why it is important to keep dependencies between different organizational units at bay.

❑ Take a look at file-level organization of code, partial types, and how all this affects formsinheritance in Visual Basic.

NamespacesMany real-life projects can amass hundreds, if not thousands, of types. Without namespaces, it wouldbe difficult for programmers to find their way in projects that large. Namespaces let you group related

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 371

Page 407: Professional Refactoring in Visual Basic

types and avoid name collisions. Each class (or any other type-like interface, structure, or enumeration, andso on) has a full name that consists of the type name itself plus the name of the namespace they belong to.For example, the ArrayList class belongs to the System.Collections namespace. While namespace isactually more than simply a label for a class, it is important that its name is chosen wisely.

Naming Guidelines and Namespace OrganizationOne of the most important things when creating namespaces is to choose appropriate and logical names.The guidelines for choosing namespace names do not differ that much from the guidelines for namingclasses. Names should be descriptive and clearly indicate the purpose of the namespace. For example, thesystem namespace System.IO clearly indicates that types in that namespace are used for reading and writing to files and data streams.

Therefore, placement of a type inside a certain namespace is by no means arbitrary. All types in a name-space should have a related purpose. This helps produce consistent library design and eases the use oftypes. Such organization is easily navigated with the help of IntelliSense in Visual Studio, as you canappreciate in Figure 13-1.

Figure 13-1

Nested NamespacesBecause namespaces can be nested, they can form logical hierarchies where more general types are placedfurther up in the hierarchy and more specific types are placed further down the hierarchy chain. Forexample, the System.Data namespace contains general data interface types such as IDbConnection,while System.Data.SqlClient contains implementation classes specific to the Microsoft SQL Serverdata provider such as System.Data.SqlClient.SqlConnection.

Changing the Root Namespace NameWhen you create a new project in Visual Studio, it is automatically assigned a root namespace, based on the project name. This name chosen by the IDE can be changed and need not have any relation withthe project or assembly name. The root namespace can be changed at the Project Properties page.

372

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 372

Page 408: Professional Refactoring in Visual Basic

Microsoft recommends using CompanyName.TechnologyName as a root namespace, so you can set thisto be a root namespace for your project. Remember that the root namespace is prepended to the nameyou write using a Namespace statement in your code. When you import the type to a class in anothernamespace, you must specify the full namespace, including the root namespace.

Using Import StatementsWhen you program, you should always use the full name of the class. The compiler will not find anyclass only by its short name unless the class belongs to the built-in System namespace, like String, orObject. To avoid typing long names, which would be rather tedious, you can use an Imports statementin an import section at the top of the file that enables you to specify the long name one time only orimport the containing namespace. You should avoid using full type names in the body of your type,because they make the code harder to read. Not only does this mean more clutter in the body of the type,it also means that the Imports section will fail to provide useful dependency information when youscan your code in browsing mode.

Because you import the whole namespace by the means of an Imports statement, you should strive to maintain the number of classes in the namespace on a moderate level. Even so, a name collision canoccur. Name collision happens when two or more imported namespaces contain the type with the samename. In those cases, you can use the alias part of an import statement to disambiguate collided names.For example:

Imports System.XmlImports ExtendedNode = ParsingCompany.ExtendedParser.XmlNode

Because System.Xml contains XmlNode class, it collides with ParsingCompany.ExtendedParser.XmlNode name. By introducing the ExtendedNode alias for ParsingCompany.ExtendedParser.XmlNode, you can use short names for both types in the same class.

Smell: Implicit ImportsDetecting the SmellThis smell is generally easily detected by simple visual inspection. Long Pascal-casedstatements such as System.Collections.Generic.IList should stand out from therest of the code in the body of the class.

Related RefactoringUse Explicit Imports refactoring to eliminate this smell.

RationaleUsing long type names in the body of your type means more clutter and more duplica-tion in your code. It will also render an Imports section uninformative, because there isno guarantee that it contains all the namespaces used by your class.

373

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 373

Page 409: Professional Refactoring in Visual Basic

Your Visual Basic project can contain a number of namespaces. Each namespace can spawn a number ofsource files and even a number of projects, but the most common way to organize namespace is to placeit inside the single project. That means that when you compile your project, your namespace will be con-tained inside the assembly, and this will convert the assembly in the unit of release and deployment.

Refactoring: Explicit ImportsMotivationUsing long type names in your code adds more clutter to your code and makes it diffi-cult to read. It also means that you cannot count on the Imports section to give youthe full breakdown of your type’s dependencies. Finally, it means more duplicationthat can be avoided by the means of import statements.

Related SmellsUse this refactoring to eliminate Implicit Imports smell.

Mechanics1. Start by adding the Imports statement for the type that was used

implicitly.

2. Eliminate the full name part from the type that was used implicitly.

3. Perform textual search on the imported namespace in order to discoverother instances of implicit imports. If more instances are discovered, pro-ceed to eliminate the namespace part from the name of the type.

BeforeOption Explicit OnOption Strict On

Public Class PurchaseHistory

Private itemsValue As _System.Collections.Generic.IList(Of Item)‘...

End Class

AfterOption Explicit OnOption Strict On

Imports System.Collections.Generic

Public Class PurchaseHistory

Private itemsValue As IList(Of Item)‘...

End Class

374

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 374

Page 410: Professional Refactoring in Visual Basic

AssembliesAn assembly is the compiled product of your VB project. Assemblies can contain a number of namespacesthat can contain a number of types. Besides compiled code, each assembly contains a Win32 header, a CILheader, type metadata, an assembly manifest, and optionally, resources as images and string tables if addedto the assembly. Assemblies are large, physical building blocks. In .NET you construct your applications bycombining different assemblies.

If you choose Windows Application as the application type of your assembly, the compiler generates anexecutable file, and you can run your process assembly as an application under Windows. Double-clickingthe file initiates the execution by launching a startup object that can be Windows form or a shared SubMain method. If you choose Class Library as the assembly application type, then the compiler generates adynamically linked library, and you can invoke and use this assembly from some other assembly. This wayyou can reuse the compiled assembly without ever seeing the code that was used to produce it. This type ofreuse is known as binary reuse, and it has a number of benefits compared to reuse on the code level.

Binary ReuseImagine you have programmed a number of classes that resolve important functionality in your applica-tion. Then you start working on some other application, and you are faced with almost the same prob-lem. Obviously, because you have already programmed the code that solves this recurring problem, youwould not want to write the code all over again. One option is to add all the source files from originalproject to the new project and in this way reuse the code. All types, including ones that were developedfor the first project are compiled into the new assembly. This type of reuse is known as source-based reuse.

Another option is to add a reference to the original project’s compiled assembly and to reuse the func-tionality provided by the assembly. This type of reuse is known as binary reuse. Binary reuse is in manyways superior to source-based reuse. The next sections take a look at some of the benefits that binaryreuse brings to your development process.

Encapsulation and ModularityThe great thing about programming is that you can count on many ready-made building blocks whenyou construct your application. Because you can compose your application out of different pieces, youcan attack bigger problems by dividing them into smaller ones. Many times you can find assemblies thatsolve many of the problems you need to resolve in order to construct your application. In this way theassembly becomes the largest building block of your development process.

Because assembly is a binary, most of the time you do not have access to the code that was used to createit. Generally, you get the documentation, you can see types and signatures in an object browser, and youcan count on help from IntelliSense in order to make use of the assembly. However, code and internalimplementation details are hidden from your view. At first, this can be seen as a limitation, and youmight be tempted to look into the code of assembly that you use.

As a matter of fact, not having access to source code of an assembly is just another level of encapsulation. In this way an enormous amount of complexity is taken from your shoulders. Just imagine a project whereyou would have the code for System.IO and System.Data namespaces inside it. This would raise the linecount and the complexity of your project by several orders of magnitude. Even the simplest program mightturn unmanageable once you start to suspect that there is a bug somewhere inside the System namespaceand place the breakpoint inside the code in order to investigate it.

375

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 375

Page 411: Professional Refactoring in Visual Basic

Versioning When you use someone else’s assembly, you expect it to be tried and tested, well documented, and with a well-designed API that is easy to use. Not only this, but you can rightfully expect the assembly’s creator to maintain the assembly. If there is a bug in the assembly, the creator should provide a new release thatresolves the bug. And because the creator is in the business of making software, it is only logical to expectthe assembly to evolve in time and that the creator will provide more and better functionality in the nextmajor release of the assembly. So, you need to be able to use assemblies that change in time, but you alsowish to avoid the problems that can result from the evolution of assemblies that you use:

❑ Because the assembly interface can change in time, you do not want your application to breakwhen a new version of the assembly you use is released.

❑ You do not want to be forced to recompile and redistribute your assembly just so you can use aminor new bug-fixing version of some assembly that you use.

❑ Finally, you want to be able to manage your own schedule of assembly upgrades.

Because in .NET, assemblies are versioned and assembly versioning mechanisms are built into the .NET run-time, you can avoid all of the aforementioned problems. You can declare that your assembly depends on a specific version of some other assembly, and you can choose if you want your assembly to use a newer version.Just imagine the complexity of these versioning procedures if you had to perform them on source code level.

See Chapter 8 for more information on assembly versioning schema and .NET version managementmechanisms.

Memory ResourcesBecause assemblies in .NET are dynamically linked and loaded, multiple application domains can refer-ence a single assembly that can be distributed in its own file. This reduces disk memory consumptionand can reduce time to download and start the application over the Internet. Each assembly is loadedonly once into the computer RAM. This means that multiple applications running and using the sameassembly at the same time will not provoke the assembly to be loaded multiple times, resulting in moreefficient use of system resources.

Strong SecurityAssemblies can be given strong names and can be digitally signed. This way, you can guarantee thatyour application is not using some other assembly that happened to have the same name as the oneused by your application and that could have been maliciously put in place of original assembly. Youcan also guarantee that the original assembly has not been tampered in any way and that when a newversion is provided, it is indeed provided by the same author you trust. You can also define permis-sions and security policies for your assemblies. The comprehensive set of security features makesassemblies safe for use and distribution even in the most demanding environments like the Internet.

Intellectual Property ProtectionAssemblies do not contain source code. When obfuscated, they can be extremely difficult to reverse-engineer. This makes an assembly an excellent platform for commercial software distribution. It alsoadds an additional level of security to your project, because no one can analyze the original source insearch for possible vulnerabilities. You can rest assured that generating the source code from the binaryand then using and modifying the code in order to compile malicious surrogate versions of the assemblywill be extremely difficult and complex.

376

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 376

Page 412: Professional Refactoring in Visual Basic

Multilanguage ReuseAn assembly programmed in any .NET language can be referenced from any .NET assembly no matterwhat language it is programmed in. This way it is possible for an assembly programmed in Visual Basicto reuse code programmed in C# or managed C++. This brings additional flexibility to your develop-ment process and opens up the field for reuse in .NET.

Now that you have seen some of the benefits of binary reuse, you are probably thinking about assem-bly organization. How do you decide on what namespaces the assembly should contain? And, whatcriteria should you apply when placing types inside the namespaces? The next section addresses those questions.

Namespace Organization GuidelinesAs projects grow in size, using classes as the sole organizational unit soon becomes insufficient. Thenamespace is a higher-level organizational unit that can group classes. The design of namespaces evolvesover time, as a result of changes in class organization that affect the way you build, maintain the system,and perform reuse. Sometimes, these forces can work against each other, or in time the balance canchange. So, it’s important to start by looking into how namespace organization affects the maintainabilityof your code.

MaintainabilityIn Chapter 11 you saw how the Single Responsibility Principle works out on classes. It states that a class should have only one reason to change. The same principle can be applied to the namespacelevel. If there are new requirements for changes, it is best if changes can be limited to a minimumnumber of namespaces. If classes are conceptually closely related or, because of the way they areimplemented, they belong inside the same namespace, then this principle applied to namespacesmeans that classes belonging to same namespace should have the same motive for change.

Smell: Large NamespaceDetecting the SmellUse class view to detect this smell. If you expand the namespace and it shows a largelist of classes, you should investigate further such namespace organization.

Related RefactoringUse Move Class to Namespace and Extract Namespace refactorings to eliminate this smell.

RationaleNamespaces are higher-order organizational units. By using them improperly, youreduce the maintainability and reusability of your code. A large namespace, especiallyif coupled with a non-coherent namespace, is poised to become a magnet and hotbedfor sprouting dependencies.

377

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 377

Page 413: Professional Refactoring in Visual Basic

ReuseThe first principle regarding reuse and class distribution is the Reuse-Release Equivalence principle.Reusable classes are not just programmed and compiled; such classes have to be supported by an inte-gral development process. In order to be able to put versioning and release practices in place, youshould separate classes intended for reuse from classes not intended to be reused.

Object-Oriented Design Principle: Reuse-Release EquivalenceIn order to be able to successfully reuse classes, it is not enough that the class is wellwritten and well designed. Some development process considerations, besides puresoftware design arguments, have to be taken into account. When you reuse someclasses, you have to be sure that you will get support when you need it and that anybugs you might encounter will be resolved. But first of all, you need a mechanism thatwill make it possible to identify the exact version of the classes you are using. Reusableclasses have to be released with versioning policies in place and in such a manner sothat the programmer using the classes can decide what version to use and when to perform an upgrade.

DefinitionTo quote Robert C. Martin from his book Agile Software Development: Principles, Patterns,and Practices (Prentice Hall, 2002), “The granule of reuse is the granule of release.”

As a consequence, you should keep classes intended for reuse together and apart fromclasses not intended for reuse.

Smell: Non-Coherent NamespaceDetecting the SmellThis smell can often be discovered indirectly. If you take a look at the package diagram,this namespace can have a large number of incoming and outgoing dependencies.

Related RefactoringUse Move Class to Namespace and Extract Namespace refactorings to eliminate this smell.

RationaleA non-coherent namespace can come in many forms. For example, it can be a name-space that mixes:

❑ Classes intended for reuse with classes not intended for reuse

❑ Classes with different responsibilities

❑ Classes with different levels of abstractness and stability

378

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 378

Page 414: Professional Refactoring in Visual Basic

Distinguishing classes intended for reuse from classes programmed without such a purpose in mind isonly the first indicator of how to organize your classes through namespaces. Different groups of classesare reused for different purposes. For example, a ShoppingBasket class can be used while building anonline store application whereas an IDbConnection interface can be used for interacting with a data-base store in any application that needs to provide persistence features.

However, as it happens, some classes are reused together. It is highly probable that if you use aShoppingBasket class, you will also use a BasketItem class. On the other hand if you use anIDbConnection interface, you will also use an IDbCommand interface. This leads to the following con-clusion: certain related classes are reused together. When you place classes inside namespaces, youshould try to put classes that are reused together inside the same namespace.

ExampleWhile programming an online store purchase form, you create a new zip code validatorclass and you place it in the same namespace and assembly as the web GUI classes.

Namespace BookStore.Web.PurchasePublic Class PurchaseForm

‘...End Class

Public Class ZipValidator‘...

End ClassEnd Namespace

The problem with such partitioning is that a reusable ZipValidator class is placedinside the same namespace and assembly as other classes not intended to be reused. Ifyou reuse the ZipValidator, you then have to accept that versioning and releasecycle that is influenced by other non-reusable classes, if such mechanisms are at all putin place for the GUI classes.

Namespace BookStore.Web.PurchasePublic Class PurchaseForm

‘...End Class‘...

End Namespace

Namespace Web.Common.ValidatorsPublic Class ZipValidator

‘...End Class‘...

End Namespace

By placing the validator class together with other reusable classes, you are not suscep-tible to the effects that changes in GUI classes can produce. This way the validator classis part of the namespace intended for reuse and with the release cycle and versioningpolicies intended and planned for reuse. Needless to say, just placing the class insidethe right namespace cannot fix your development process; correct development prac-tices have to be put in place.

379

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 379

Page 415: Professional Refactoring in Visual Basic

As an additional illustration, take a look at the existing .NET Framework classes in the System.Collections.Generic namespace. If you use the Dictionary class, you will most probably also use the KeyValuePair and the KeyNotFoundException classes.

Refactoring: Move Class to NamespaceMotivationSometimes, dependency problems and the Non-Coherent Namespace smell can beresolved by moving the class to proper namespace.

Related SmellsUse this refactoring to eliminate Non-Coherent Namespace and Cyclic Dependencies smell.

Mechanics1. If the originating and target namespaces are in different assemblies, start

by moving the source file to the target assembly. (In case there are multipletypes in the source file, you will need to perform Move Type to File refactor-ing before this step.)

2. Change the namespace declaration in the class to targeted namespace.Remember that assemblies often declare the root namespace.

3. Search for references to the type. Use the compiler to locate places wherethe type was used. It will display a “Type typename is not defined” mes-sage. Modify the imports section so it imports the namespace that the typenow belongs to.

BeforeNamespace Web.ShoppingBasket

Public Class PerishableContainer‘...

End ClassPublic Class Basket

‘...End Class‘...

End Namespace

AfterNamespace Web.ShoppingBasket

Public Class Basket‘...

End Class‘...

End Namespace

Namespace Common.ContainersPublic Class PerishableContainer

‘...End Class‘...

380

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 380

Page 416: Professional Refactoring in Visual Basic

So what is the benefit of placing classes reused together in the same package? The response to this ques-tion has to do with the issue of dependencies. The next section takes a look at what dependencies areand why they are important for your development process and application architecture.

Dependency ConsiderationsWhen you reference a certain type in your code, you are establishing the dependency between the typeyou referenced and the type that is making the reference. The effect of dependency is the following:

❑ If the type that your type depends upon changes, you most probably have to change and recom-pile the dependent type.

❑ Even when the change in the type that other class depends upon is implementation-only, meaningthat no signatures in the type have changed, you still have to test dependent type in order to makesure that the latest change didn’t result in a new bug.

❑ If the dependency related types belong to different namespaces that means that there is adependency relationship between the containing namespaces. And if the containing namespacesbelong to different assemblies, that means there is also a dependency relationship between thecontaining assemblies.

The effect of dependencies on the architecture of our application and development process in generalis profound. This makes dependency management one of the most important aspects of the large-scaledesign of your application. Take a look now at how dependencies influence some crucial aspects ofdevelopment process.

Build Process, Testability, and DistributionIn order to compile a dependent class, a compiler needs to have a compiled version of the class thatdependent class is dependent upon. If the dependency is circular, it turns into kind of a chicken oregg question for the compiler. Visual Studio will not even let you establish cyclic dependencybetween projects in the solution.

However, you can still establish cyclic dependencies by referencing the compiled assembly. The effect of cyclic dependencies is such that you are not able to normally control the versioning process in your solution.

Imagine that two projects, A and B, both depend on each other and a group of programmers is workingon these two projects in parallel. If you try to release new versions of these two projects, you will findthat you cannot release them independent of each other. The solution is in arduous integration work thatin practice converts these two projects into a single project physically divided into two assemblies. Thesetwo assemblies are still a single monolithic software product from a development process point of view.The software has to be compiled, tested, and distributed together.

When there are no circular dependencies in your solution, then each project can follow its own releasetimeline. Each dependent project can choose when to make use of the new version of the project itdepends upon.

381

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 381

Page 417: Professional Refactoring in Visual Basic

Refactoring: Extract NamespaceMotivationNamespaces containing too many types increase the clutter and make it more difficultfor programmers to find and use classes. Large namespaces will negatively impactreuse and maintenance characteristics in your code. Such namespaces can be reducedin size by namespace extraction.

Sometimes, namespace extraction can resolve the problem of cyclic dependencies. Byextracting offending classes to a separate namespace, you can make both namespacesdepend on the extracted namespace, instead of having them depend on each other.

Namespace is a higher order organizational unit. By placing related classes in separatenamespaces you improve the organization and coherence of your code.

Related SmellsUse this refactoring to eliminate the Large Namespace, Non-Coherent Namespace, andCyclic Dependencies smells.

Mechanics1. Decide the name you want to give to the extracted namespace and create a

directory to store classes in this namespace.

2. If you have more than one class in a single file, perform Move Type to Filerefactoring where necessary.

3. For each class that belongs to the new extracted namespace, move thatclass’ file to the new directory created for the namespace.

4. For each class that belongs to the new extracted namespace, change thenamespace declaration so it corresponds to the newly extracted namespace.

BeforeNamespace RentAWheels.Business

Public Class Vehicle‘...

End Class

Public Class Branch‘...

End Class

Public Class VehicleCategory‘...

End Class

Public Class User‘...

End Class

Public Class Profile

382

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 382

Page 418: Professional Refactoring in Visual Basic

Breaking Dependency CyclesFortunately, there are methods to deal with dependencies in your projects. One such method can help youinvert dependency and in that way break the dependency cycle. Cycles often involve multiple projects.These complex relationships are best analyzed by viewing UML package diagrams that can show name-space structures and their relationships and that are being generated by some tool from the same codethat is being executed. In order to keep things simple, you should use only two namespaces belonging totwo different assemblies for this illustration.

‘...End Class

Public Class Permission‘...

End Class

End Namespace

AfterNamespace RentAWheels.Business

Public Class Vehicle‘...

End Class

Public Class Branch‘...

End Class

Public Class VehicleCategory‘...

End Class

End Namespace

Namespace RentAWheels.Users

Public Class User‘...

End Class

Public Class Profile‘...

End Class

Public Class Permission‘...

End Class

End Namespace

383

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 383

Page 419: Professional Refactoring in Visual Basic

Imagine you have a typical GUI namespace whose classes are interacting with a faxing service inanother namespace. The mock-up code is shown in Listing 13-1.

Listing 13-1: Cyclic Dependency between GUI and Faxing Service Namespaces

Namespace FaxingPublic Class FaxService

Private faxDevice as FaxDevicePublic Sub SendFax(ByVal job As FaxJob)

TryfaxDevice.dial(job.number)faxDevice.transmit()faxDevice.endTransmission()

Catch ex As ExceptionDim errorForm As ErrorForm = New ErrorForm()errorForm.ShowErrorMessage(“Faxing failed:” + ex.Message)

End TryEnd Sub‘...

End ClassEnd Namespace

Namespace GUIPublic Class FaxingForm

Private Sub SendFax_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles SendFax.Click

Dim faxService As New FaxServicefaxService.SendFax(Me.Job)

End Sub‘...

End ClassEnd Namespace

Smell: Cyclic DependenciesDetecting the SmellThis smell is not easily detected on any but the smallest projects. Some code analysistools are capable of identifying dependency cycles. One such tool is NDepend(www.ndepend.com).

If the cycle spawns projects, Visual Studio refuses adding cyclical references betweenthe projects in the solution. However, you are still able to reference the compiledassembly in a cyclical manner.

Related RefactoringUse Move Class to Namespace and Extract Namespace refactorings and invert depend-encies in order to eliminate cyclical dependencies from your project.

RationaleCyclic dependencies work against the modularity of your code. Items joined through cyclic dependencies have to be tested, released, and reused together.

384

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 384

Page 420: Professional Refactoring in Visual Basic

If you were to present this in a diagram depicting dependencies between namespaces, it would looksomething like Figure 13-2.

Figure 13-2

Even from the simple look of the diagram, it is obvious that there is a problem with the design. The Faxingnamespace groups classes related to faxing functionality. The GUI namespace groups forms that renderuser interface and contains this particular application’s logic. Unfortunately, there is a circular dependencypresent between the two. Where does this problem come from?

If you were to design the faxing service as a reusable component, then depending on the particularapplication and its GUI is not such a good idea. If you analyze the faxing service code carefully, you cansee that it uses an ErrorForm to inform users that something went wrong with a faxing operation. Thisis the source of circular dependency and at the same time is the weakest link between the two. You canthen try to eliminate this dependency.

Inverting DependenciesYou need to eliminate the FaxingService dependency on ErrorForm, but at the same time you need tokeep the functionality that enables you to inform the user of any problem with the faxing operation. Thesolution is to invert the dependency between FaxingService and ErrorForm.

Consider extracting the error-reporting code to a separate method and naming it OnError:

Public Sub OnError(ByVal message As String)Dim errorForm As ErrorForm = New ErrorForm()errorForm.ShowErrorMessage(“Faxing failed:” + ex.Message)

End Sub

This method is now called from a Catch block. Now, you can extract interface with this method. Name it ErrorReport. But, instead of FaxService implementing this method, you can let clients interested inerror messages from FaxService implement this method.

Instead of using ErrorForm, FaxingService can use a newly defined interface that has the same purposeand that resides in the same namespace as FaxingService. Objects interested in getting information about faxing problems can implement this interface and subscribe to listening for errors messages fromFaxingService. The code for the solution is shown in Listing 13-2.

Listing 13-2: Cyclic Dependency between GUI and Faxing Service Namespaces Inverted

Namespace FaxingPublic Interface ErrorReport

Sub OnError(ByVal message As String)

Continued

FaxingGUI

385

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 385

Page 421: Professional Refactoring in Visual Basic

Listing 13-2: Cyclic Dependency between GUI and Faxing Service Namespaces Inverted (continued)

End Interface

Public Class FaxServicePrivate faxDevice as FaxDevicePrivate errorReportValue As ErrorReportPublic Sub SendFax(ByVal job As FaxJob)

TryfaxDevice.dial(job.number)faxDevice.transmit()faxDevice.endTransmission()

Catch ex As ExceptionErrorReport.OnError(ex.Message)

End TryEnd Sub

Public Property ErrorReport() As ErrorReportGet

Return errorReportValueEnd GetSet(ByVal value As ErrorReport)

errorReportValue = valueEnd Set

End Property‘...

End ClassEnd Namespace

Namespace GUIPublic Class FaxingForm

Implements ErrorReportPrivate Sub SendFax_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles SendFax.Click

Dim faxService As New FaxService‘subscribe to error messagesfaxService.ErrorReport = MefaxService.SendFax(Me.Job)

End Sub

Public Sub OnMessage(ByVal message As String) _Implements ErrorReport.OnError

Dim errorForm As ErrorForm = New ErrorForm()errorForm.ShowErrorMessage(“Faxing failed:” + message)

End Sub‘...

End ClassEnd Namespace

The cycle between the two namespaces is eliminated. The new state of affairs is depicted graphically inFigure 13-3.

386

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 386

Page 422: Professional Refactoring in Visual Basic

Figure 13-3

When you are looking to break cycles, the best methodology is to search for the weakest link. If youbreak the cycle at one point, it means you have done away with the cycle. Dependency inversion is apowerful tool for controlling spiraling dependencies. It can be applied to break circular dependenciesand reduce dependencies in general. While dependencies have a negative impact on your design, mak-ing it more rigid and less modular, they cannot be avoided altogether.

Now it’s time to take a look at another dimension of source code organization, a more physical one —how you place the code inside a physical file and folder structure.

Visual Basic Project F ile Structure Organization

Visual Basic is very flexible in regards to organizing your project’s file structure. You can place yoursource code files inside an arbitrary directory structure, and you can place an arbitrary number of typesinside a single source code file. You can even distribute a single type over more than one source codefile, as you will soon see in the section dedicated to partial classes later in the chapter.

Object-Oriented Design Principle: Acyclic Dependencies PrincipleCyclic dependencies work directly against the modularity of your software. If you letsuch dependencies grow, you soon lose the benefits of modular design. You have to starttreating all the projects in your solution as a single project, meaning that you cannot test,release, and reuse each assembly in an independent manner. Your build process suffers,because all dependent namespaces and assemblies have to be released together.

DefinitionIn the words of Robert C. Martin, Agile Software Development: Principles, Patterns, andPractices (Prentice Hall, 2002), “Allow no cycles in the package-dependency graph.”

In VB, you can interpret a package as a namespace.

ExampleCyclic dependencies are best observed on package diagrams. Take a look at Figure 13-2for the simplest case of cyclic dependencies.

FaxingGUI

387

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 387

Page 423: Professional Refactoring in Visual Basic

With all this flexibility at hand, you really need to choose a uniform schema for project file organization.One such schema that can help you avoid clutter in Solution Explorer and find your way around sourcecode files faster is the following:

❑ Mimic the namespace hierarchy with the directory hierarchy. For example, place the PurchaseForm class belonging to BookStore.Web.Purchase namespace inside the[Project Root Directory]\BookStore\Web\Purchase directory. You can ignore the rootnamespace for this purpose.

❑ Place a single type inside a single source code file. This lets you specify an Options and Importssection in such a way that it refers to single type. It also makes it easier to move the class toanother namespace or assembly, because you can simply move the file in Solution Explorer.

❑ Use the class name for the source file name. For example, if the file contains PurchaseForm,name it PurchaseForm.vb.

In case you have followed some different schema and now you wish to place a single type inside a singlefile, you can use the Refactor! to help you with this task. This refactoring is called Move Type to File.

Move Type to File Refactoring in Refactor!As you would expect, this refactoring becomes available only if the source code file that you work withcontains more than one type. Even then, the refactoring is available only for types whose name is differ-ent from the name of the source file. Refactoring can be invoked by right-clicking the class declaration,as shown in Figure 13-4. After the refactoring is invoked, it adds the new file to your project, uses theclass name for the source code file name, and moves the code of the class on the move to the new file. It moves the complete code of the class to the new file, including the namespace and complete importsection declaration.

Figure 13-4

388

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 388

Page 424: Professional Refactoring in Visual Basic

Refactoring: Move Type to FileMotivationMultiple types in a single source file can make your code more difficult to work with.Because a file is the smallest unit under source code control, by placing multiple typesin single file, you increase the probability that more than one developer works on thesame file and, thus, increase the probability of conflicting changes.

Multiple types in a single file make some large-scale organizational refactorings suchas Extract Namespace and Move Class to Namespace more difficult. While these refac-torings do not have to be performed on the file level, it is recommended. Physical proj-ect structures where directory and file hierarchy mimic namespace hierarchy makeyour project organization more logical and coherent.

Related SmellsUse this refactoring to place types in separate source files and to prepare your projectfor refactorings like Move Type to File and Extract Namespace.

Mechanics1. Start by creating new source file for the type you wish to move to a sepa-

rate file. Use the type name for the newly created file name.

2. Cut and paste type code to new file.

3. Optimize imports. Remove imports that are not used any more in the originalfile and be careful to copy to new file only those imports that are used by thetype you have just moved.

BeforeVehicle.vb file content:

Public Class Vehicle‘...

End Class

Public Class Branch‘...

End Class

AfterVehicle.vb file content:

Public Class Vehicle‘...

End Class

Branch.vb file content:

Public Class Branch‘...

End Class

389

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 389

Page 425: Professional Refactoring in Visual Basic

After you perform this refactoring, it is advisable that you add Option Strict and Option Explicitdeclarations to the top of the newly added file. You should also inspect Imports section for any unusedimports, because the tool simply copies the import section from the original file to the new file. BecauseRefactor! is copying original source, some imports used only by the class that stayed put in the originalclass will find their way into the new file. Remove unused imports from the new file.

Next, you will turn to another peculiar schema for source code organization. Partial classes let youspread single type across multiple source code files.

Partial ClassesPartial classes are another interesting Visual Basic feature that appeared in the 2005 version of the lan-guage. This feature permits one class to span more than one source code file. It is primarily intended forcode generator tools like Windows Forms Designer. All tool-generated code can be placed in separatefile, out of the programmer’s sight, because it was never intended to be manipulated directly by pro-grammer anyway. In this sense, partial classes are similar to the Region directive of VB. (Region direc-tives let you mark the section of source code so it can be collapsed and hidden in the VB editor).

One caveat about partial types: You should not use partial types as a replacement for traditional object-oriented techniques. Remember that a partial type spread across several source code files is in the endstill a single type and will be treated by the compiler and client programmers as such. If you let such a type grow out of proportions, it will suffer all the problems that any other class suffering Large Classsmell would: it would be difficult to maintain and reuse, prone to change, and would not abide withSingle Responsibility Principle (SRP) or Open-Closed Principle (OCP).

Partial classes are related to another feature in VB. This feature has to do with Windows forms inheritance.

Inherited FormInherited Form is a built-in Visual Studio template that helps you inherit the form class. If you alreadyhave a form class in your solution and you decided to invoke Inherited Form template through AddNew Item dialog, Visual Studio displays the Inheritance Picker dialog and prompts you to select the par-ent form class. After you select the parent form, Visual Studio adds another form to your project thatinherits the selected parent. In design view, you can see all the controls that were placed on the parentform, but they are marked with special lock icon. You cannot modify these controls in the child form. Ifyou modify the controls placed on parent form, these changes are reflected in the child form only afteryou build the project containing the parent form.

For the Visual Basic compiler, the form class is the same as any other class. The same is true for an inher-ited form. However, if you open inherited form’s source code, you find an empty file. So, how is theinheritance relationship between the forms realized in source code?

To find out, create an ad hoc Windows Application project and perform the following steps:

1. Add an empty Form1 form to the project.

2. Build the project.

390

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 390

Page 426: Professional Refactoring in Visual Basic

3. Add a new inherited form item to the project and select Form1 as the component you wish toinherit from.

4. Check that the text property in the recently added child Form2 is set to Form2.

In order to see the source code that establishes inheritance relationship between the forms, you have toopen the designer-generated source part of the form partial class. You can display this file if you activatethe Show All Files option in the Solution Explorer in Visual Studio and expand the view of Form2. Thefile name is Form2.Designer.vb.You can see that the form’s declaration contains the Inherits state-ment that establishes an inheritance relationship between the two forms. Take a look at Listing 13-3.

Listing 13-3: Empty Inherited Form2 Designer-Generated Code

<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _Partial Class Form2

Inherits InheritedForms.Form1

‘Form overrides dispose to clean up the component list.<System.Diagnostics.DebuggerNonUserCode()> _Protected Overrides Sub Dispose(ByVal disposing As Boolean)

If disposing AndAlso components IsNot Nothing Thencomponents.Dispose()

End IfMyBase.Dispose(disposing)

End Sub

‘Required by the Windows Form DesignerPrivate components As System.ComponentModel.IContainer

‘NOTE: The following procedure is required by the Windows Form Designer‘It can be modified using the Windows Form Designer. ‘Do not modify it using the code editor.<System.Diagnostics.DebuggerStepThrough()> _Private Sub InitializeComponent()

Me.SuspendLayout()‘‘Form2‘Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)Me.ClientSize = New System.Drawing.Size(292, 266)Me.Name = “Form2”Me.ResumeLayout(False)

End Sub

End Class

You can compare this code with designer-generated code for Form2. As you can see, this code does notdiffer that much from typical form declaration. The principal difference is that the parent Form1 form isinheriting System.Windows.Forms.Form class. I guess it is fair to say that with this form inheritancehas been largely demystified. However, form inheritance displays one abnormality inside the VisualStudio environment.

391

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 391

Page 427: Professional Refactoring in Visual Basic

Abstract Form InheritanceBecause form classes can be considered normal Visual Basic classes, they should support all legal codemanipulation that is permitted by Visual Basic language. When using inheritance, you might well wishto make the form abstract. Using the example from the previous section, you can try to do so:

1. First, change the project’s startup object to Form2.

2. Modify the Form1.Designer.vb by adding a MustInherit statement to the form’s declaration.The code should now look like this:

Partial MustInherit Class Form1Inherits System.Windows.Forms.Form

3. Close all the editor windows in Visual Studio.

4. Build the project.

As you can see if you try this by yourself, everything works out fine. If you take a look at the Error Listwindow, it should be empty. And if you run the project, Form2 is displayed.

Now you can try to edit the Form2 in the Windows Form Designer. If you click Form2 in SolutionExplorer twice, instead of displaying an edit view of Form2, you get a window displaying the follow-ing error message:

The designer must create an instance of type ‘InheritedForms.Form1’ but it cannotbecause the type is declared as abstract.

If you have read this message carefully, you might think I made a mistake somewhere in the process ofdescribing the previous steps. Designer is saying it cannot create an instance of Form1 and you clickedthe Form2?

As a matter of fact, there was no error. The Windows Forms Designer is implemented in such a way that itinstantiates the parent form in the form hierarchy when displaying the Form Designer window. Becausein this example Form2 inherits Form1, the designer tried to instantiate Form1. Because the Form1 wasmarked as abstract, the designer could not create an instance of it, and the error was produced.

This is an unfortunate limitation of the Window Forms Designer tool and Visual Studio. There is no reasonwhy you shouldn’t be able to program abstract parent windows forms and then design nonabstract children.Fortunately, there is a workaround for this problem.

Delegating Abstract Form Work to a Form Helper ClassImagine you are programming reports in your application, and you detect that all forms that are used to display reports have a number of identical members. You decide to extract the AbstractReportFormsuperclass and make all concrete report forms inherit the abstract report form. Each concrete report formhas a different way of recollecting data for report generation, so you mark the AddReportData methodas abstract using MustOverride keyword and therefore you make AbstractReportForm abstract(MustInherit).

392

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 392

Page 428: Professional Refactoring in Visual Basic

Everything is working out fine, but the problem is with designing concrete report forms. A designer isreporting an error because AbstractReportForm is abstract. Is there a way that you could place com-mon code inside the single parent class and still design forms in Windows Designer?

The solution comes in the form of Replace Inheritance with Delegation refactoring. The parent reportform need not leave abstract methods that child forms have to implement. Instead, it can delegate allwork to an AbstractReportHelper class. While Windows forms cannot be abstract, they can use otherabstract classes without any problem. So, a concrete report form can create an instance of a concretehelper class that overrides all abstract members declared in AbstractReportHelper. The code inListing 13-4 illustrates this solution.

Listing 13-4: Delegating Work to AbstractHelper to Keep the Form Non-Abstract

Option Explicit OnOption Strict On

Public Class GeneralReportFormPrivate helperValue As AbstractReportHelperPrivate Sub ViewReport_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles ViewReport.Click

Helper.SelectReportType()Helper.AddReportData()Helper.ViewReport()

End SubProtected Property Helper() As AbstractReportHelper

GetReturn helperValue

End GetSet(ByVal value As AbstractReportHelper)

helperValue = valueEnd Set

End Property‘...

End Class

Public MustInherit Class AbstractReportHelperPrivate formValue As GeneralReportFormPublic Sub New(ByVal form As GeneralReportForm)

formValue = formEnd SubFriend Sub ViewReport()

‘... End SubFriend Sub SelectReportType()

‘...End SubFriend MustOverride Sub AddReportData()Public Property Form() As GeneralReportForm

GetReturn formValue

End GetSet(ByVal value As GeneralReportForm)

formValue = value

Continued

393

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 393

Page 429: Professional Refactoring in Visual Basic

Listing 13-4: Delegating Work to AbstractHelper to Keep the Form Non-Abstract (continued)

End SetEnd Property‘...

End Class

Public Class AccountsReportFormPublic Sub New()

MyBase.New()‘ This call is required by the Windows Form Designer.InitializeComponent()‘ Add any initialization after the InitializeComponent() call.

‘Create concrete accounts helper and pass instance of this form (Me)‘so it can read data necessary for the reportMe.Helper = New AccountsHelper(Me)

End Sub‘...

End Class

Public Class AccountsHelperInherits AbstractReportHelperPublic Sub New(ByVal form As GeneralReportForm)

MyBase.New(form)End SubFriend Overrides Sub AddReportData()

‘ Add accounts report data End Sub‘...

End Class

With this finished, you are now ready to take a look at Rent-a-Wheels and see how the latest refactoringsapply to the sample application.

Namespace Organization and WindowsForms Inheritance in Rent-a-Wheels

I start work on Rent-a-Wheels by sorting out one issue that was pending from the previous chapter. Evenwhile making changes in the previous chapter, I noticed that all administration forms have a number ofidentical members. However, I didn’t cover forms inheritance in the last chapter, so it was left for later.Now is the right moment to deal with duplicate code in the administration forms.

Extracting Parent Administration Form through Abstract Form Helper Pattern Application

This chapter has discussed issues related to Windows Forms inheritance and Window Forms Designer.You have seen how Designer generates separate files for Designer-generated code. You have also seensome of the Designer’s limitations in regards to use of abstract forms.

394

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 394

Page 430: Professional Refactoring in Visual Basic

The first graphically obvious duplication between different administration forms in the Rent-a-Wheelsapplication involves two groups of controls. The first group of controls is a navigation strip at the bottom of the forms that lets you navigate between different records in the table being maintained by the form. Thesecond group comprises the New, Save, Delete, and Reload action buttons on the right side of the form.

Extract Super Administration FormStart by adding a new Windows form named GeneralAdministrationForm to the project. Next, copy the controls from the random administration form and paste it on GeneralAdministrationForm.Accommodate controls on the form so that the position of the action and navigation button strips fit all ofthe existing administration forms. Once all specific label and text controls are added, these should be visibleand should not overlap with controls on the parent class. You can see GeneralAdministrationForm beingdisplayed by the Designer in Figure 13-5.

Figure 13-5

Move up all members identical in all the forms to GeneralAdministrationForm. Some of these identicalmethods use methods that are different in each form. For example, the navigational button First event-handler routine is identical in all forms:

Private Sub FirstItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles FirstItem.ClickIf (models.Count > 0) Then

currentObjectIndex = 0DisplayCurrentObject()

End IfEnd Sub

However, the DisplayCurrentObject method is different in each of the forms. For example, take alook at the DisplayCurrentObject in the BranchMaintenance form:

Private Sub DisplayCurrentObject()Dim branch As Branch = _CType(branches.Item(currentObjectIndex), RentAWheel.Branch)Me.Id.Text = branch.Id.ToStringMe.BranchName.Text = branch.Name

End Sub

395

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 395

Page 431: Professional Refactoring in Visual Basic

This method differs from the DisplayCurrentObject in the ModelMaintenance form:

Private Sub DisplayCurrentObject()Dim model As Model = _CType(models.Item(currentObjectIndex), RentAWheel.Model)Me.Id.Text = model.Id.ToStringMe.ModelName.Text = model.NameMe.ModelCategory.SelectedValue = model.Category.Id

End Sub

You need to have the DisplayCurrentObject method in the GeneralAdministrationForm so youcan compile the project. For the moment, declare an empty DisplayCurrentObject and change its dec-laration to Protected Overridable. This way, child forms can override this method with code specificfor the concrete administration form. The empty DisplayCurrentObject looks like this:

Protected Overridable Sub DisplayCurrentObject()

End Sub

Do the same with rest of the methods. In some cases you need to extract some event-handling code intoa separate method. For example, you can extract the NewItem_Click code to new CleanForm method.

Finally, it is time to make all administration forms inherit GeneralAdministrationForm. First, erase all thecode already existing in GeneralAdministrationForm, like all event-handling routines. Now, make eachadministration form inherit GeneralAdministrationForm. In order to do this, you have to change theDesigner-generated code. Because each form already has an Inherits declaration, all you need to do is tochange the Inherits System.Windows.Forms.Form line to Inherits GeneralAdministrationForm.Now run the application and make sure that everything works as it is supposed to.

This is already a great step forward. I have reduced a huge amount of duplicated code and streamlinedadministration form construction. However, I am not happy with leaving empty method declarations, suchas the empty DisplayCurrentObject method, in the extracted super GeneralAdministrationForm. Hadit not been for problems with the Windows Forms Designer, I’d mark this method with MustOverride andthe GeneralAdministrationForm with MustInherit.

Getting Help from Form HelperIn order to refactor the GUI layer of Rent-a-Wheels so it renders well in Windows Forms Designer, you needto introduce helper classes that contain the logic contained previously in the event-handler routines in theform classes. This way, you can extract a FormHelper class that contains all the code that is identical in all ofthe form classes and contains some abstract method declarations. The GeneralAdministrationForm endsup being an empty shell that delegates all the work to AdministrationFormAbstractHelper. Take a lookat GeneralAdministrationForm in Listing 13-5.

Listing 13-5: GeneralAdministrationForm Delegating toAdministrationFormAbstractHelper

Option Explicit OnOption Strict OnPublic Class GeneralAdministrationForm

Private helperValue As AdministrationFormAbstractHelper

396

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 396

Page 432: Professional Refactoring in Visual Basic

Listing 13-5: GeneralAdministrationForm Delegating toAdministrationFormAbstractHelper (continued)

Private Sub RightItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles RightItem.Click

Me.helper.RightItem_Click()End Sub

Private Sub LeftItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles LeftItem.Click

Me.helper.LeftItem_Click()End Sub

Private Sub FirstItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles FirstItem.Click

Me.helper.FirstItem_Click()End Sub

Private Sub LastItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles LastItem.Click

Me.helper.LastItem_Click()End Sub

Public Property Helper() As AdministrationFormAbstractHelperGet

Return helperValueEnd GetSet(ByVal value As AdministrationFormAbstractHelper)

helperValue = valueEnd Set

End Property

Private Sub GeneralAdministrationForm_Load(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles MyBase.Load

If Not IsNothing(Me.Helper) Then Me.Helper.Form_Load()End Sub

Private Sub NewItem_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles NewItem.Click

Me.Helper.NewItem_Click()End Sub

Private Sub Save_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Save.Click

Me.Helper.Save_Click()End Sub

Private Sub Delete_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Delete.Click

Me.Helper.Delete_Click()

Continued

397

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 397

Page 433: Professional Refactoring in Visual Basic

Listing 13-5: GeneralAdministrationForm Delegating toAdministrationFormAbstractHelper (continued)

End Sub

Private Sub Reload_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Reload.Click

Me.Helper.Reload_Click()End Sub

End Class

All the work has been moved to AdministrationFormAbstractHelper. Because the helper classdoes not contain any GUI elements and therefore won’t be manipulated in Windows Designer, it canbe marked as abstract (MustInherit) and can contain abstract members (MustOverride). The codefor AdministrationFormAbstractHelper is in Listing 13-6.

Listing 13-6: AdministrationFormAbstractHelper Code

Option Explicit OnOption Strict On

Public MustInherit Class AdministrationFormAbstractHelper

Private currentObjectIndexValue As IntegerPrivate objectsValue As IList

Public Sub New()CreateData()

End Sub

Public Sub Form_Load()LoadObjects()If (Me.Objects.Count > 0) Then

CurrentObjectIndex = 0DisplayCurrentObject()

End IfEnd Sub

Public Sub RightItem_Click()If (Objects.Count > CurrentObjectIndex + 1) Then

CurrentObjectIndex += 1DisplayCurrentObject()

End IfEnd Sub

Public Sub LeftItem_Click()If (CurrentObjectIndex - 1 >= 0 And Objects.Count > 0) Then

CurrentObjectIndex -= 1DisplayCurrentObject()

End If

398

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 398

Page 434: Professional Refactoring in Visual Basic

Listing 13-6: AdministrationFormAbstractHelper Code (continued)

End Sub

Public Sub FirstItem_Click()If (Objects.Count > 0) Then

CurrentObjectIndex = 0DisplayCurrentObject()

End IfEnd Sub

Public Sub LastItem_Click()If (Objects.Count > 0) Then

CurrentObjectIndex = Objects.Count - 1DisplayCurrentObject()

End IfEnd Sub

Public Sub NewItem_Click()CleanForm()

End Sub

Public Sub Save_Click()SaveObject()Form_Load()

End Sub

Public Sub Delete_Click()DeleteObject(Objects.Item(currentObjectIndexValue))Form_Load()

End Sub

Public Sub Reload_Click()Form_Load()

End Sub

Public Property Objects() As IListGet

Return objectsValueEnd GetSet(ByVal value As IList)

objectsValue = valueEnd Set

End Property

Public Property CurrentObjectIndex() As IntegerGet

Return currentObjectIndexValueEnd GetSet(ByVal value As Integer)

currentObjectIndexValue = valueEnd Set

Continued

399

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 399

Page 435: Professional Refactoring in Visual Basic

Listing 13-6: AdministrationFormAbstractHelper Code (continued)

End Property

Protected MustOverride Sub LoadObjects()

Public MustOverride Sub DisplayCurrentObject()

Protected MustOverride Sub CreateData()

Protected MustOverride Sub CleanForm()

Protected MustOverride Sub SaveObject()

Protected MustOverride Sub DeleteObject(ByVal currentObject As Object)

End Class

Abstract methods are overridden in the concrete helper class. For example, you can seeBranchMaintenanceHelper in Listing 13-7.

Listing 13-7: BranchMaintenanceHelper Implementing Abstract Methods

Option Explicit OnOption Strict On

Public Class BranchMaintenanceHelperInherits AdministrationFormAbstractHelper

Private formValue As BranchMaintenance

Private branchData As BranchData

Public Property Form() As BranchMaintenanceGet

Return formValueEnd GetSet(ByVal value As BranchMaintenance)

formValue = valueEnd Set

End Property

Public Overrides Sub DisplayCurrentObject()Dim branch As Branch = _

CType(Objects.Item(CurrentObjectIndex), Branch)formValue.Id.Text = branch.Id.ToStringformValue.BranchName.Text = branch.Name

End Sub

Protected Overrides Sub LoadObjects()MyBase.Objects = CType(branchData.GetAll,

400

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 400

Page 436: Professional Refactoring in Visual Basic

Listing 13-7: BranchMaintenanceHelper Implementing Abstract Methods (continued)

System.Collections.Generic.List(Of Branch))End Sub

Protected Overrides Sub CreateData()Me.branchData = New BranchData

End Sub

Protected Overrides Sub CleanForm()Form.Id.Text = String.EmptyForm.BranchName.Text = String.Empty

End Sub

Protected Overrides Sub SaveObject()If (Form.Id.Text.Equals(String.Empty)) Then

Dim branch As Branch = New Branch(Nothing, _Form.BranchName.Text.ToString)branchData.Insert(branch)

ElseDim branch As Branch = CType(Objects.Item(CurrentObjectIndex), Branch)branch.Name = Form.BranchName.TextbranchData.Update(branch)

End IfEnd Sub

Protected Overrides Sub DeleteObject(ByVal currentObject As Object)branchData.Delete(CType(currentObject, Branch))

End SubEnd Class

All that is left for the concrete form is to instantiate helper. Take a look at the BranchMaintenance formin Listing 13-8.

Listing 13-8: BranchMaintenance Form

Option Explicit OnOption Strict On

Public Class BranchMaintenance

Public Sub New()

MyBase.New()Dim helper As BranchMaintenanceHelper = New BranchMaintenanceHelperMyBase.Helper = helper‘ This call is required by the Windows Form Designer.InitializeComponent()helper.Form = Me

End Sub

End Class

401

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 401

Page 437: Professional Refactoring in Visual Basic

This way, you have surpassed the limitation that Windows Forms Designer has with abstract classes. You arenow able to display and design forms in Windows Forms Designer, and you are able to use abstract classes,an important feature of your code that will leave no loopholes and “apparently” implemented methods toimplementors, as is the case if you use parent Windows forms that contain abstract logic. With this, you havefinished refactoring GUI classes. Now it’s time to do some large-scale reorganization of the project.

Namespace and Assembly ReorganizationAt this point, you have a single project with a single namespace containing all the classes. (You rememberplacing classes containing column names to a separate directory and namespace at some point earlier on,but this is just an exception to the rule.)

If you analyze some of the rules discussed in this chapter, you will see that there are two primary forcesthat can be applied to Rent-a-Wheels:

❑ Single Responsibility

❑ Reuse-Release Equivalence

The first principle is telling you that you should keep GUI classes apart from business classes apart fromdata classes. The second principle is telling you that you should keep abstract classes apart from concreteclasses. You can interpret this as having horizontal and vertical axis of separation. Figure 13-6 presentsthis schematically.

Figure 13-6

The partitions depicted in the figure looks like a good way to partition Rent-a-Wheels into assemblies. Allprojects except for the concrete GUI will be class libraries. The concrete GUI assembly will be Windowsapplication. Each of the assemblies must be revised for imports and in some cases will reference anotherRent-a-Wheels assembly. Assembly dependencies are better depicted with the component diagram shownin Figure 13-7.

One important conclusion is immediately obvious from the diagram. The application has no circulardependencies. Secondly, less abstract namespaces depend on more abstract ones. Also, there is a clearseparation of concerns between the assemblies.

While organizing namespaces, you have also used an opportunity to organize class files into directories.This makes the project easier to browse in Solution Explorer. Take a look at the Rent-a-Wheels solutionexplorer after directory reorganization in Figure 13-8.

Abstract GUI Abstract Data

Concrete GUI Concrete Business Concrete Data

402

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 402

Page 438: Professional Refactoring in Visual Basic

Figure 13-7

Figure 13-8

This looks much more orderly and better organized. And I don’t mean only the files; the whole solutionis now much more coherent. You might even go so far as to say that there are no immediate problems inthe application needing your attention. However, this does not mean that you cannot take the designeven further. That will wait for the next chapter.

SummaryThis chapter dealt with some issues that are often related to large projects. You have seen how in VisualBasic you use some higher-level organizational structures to manage your projects. Organizing typesinside namespaces and partitioning solutions into assemblies has to be done with an understanding ofthe principles governing large-scale organization.

These principles are applied in order to improve maintainability and reuse and to minimize dependenciesbetween different elements in a project. Unmanaged dependencies can have direct repercussions on yourdevelopment process.

«executable»RentAWheel.GUI.Implementation

«library»RentAWheel.Data.Implementation

«library»RentAWheel.Data

«library»RentAWheel.Business

«library»RentAWheel.GUI

403

Chapter 13: Code Organization on a Large Scale

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 403

Page 439: Professional Refactoring in Visual Basic

Luckily, you have the means to control and deal with dependencies. Minimizing dependencies betweenassemblies or namespaces can be as simple as moving a type to a proper namespace. In other cases, moreelaborate solutions, such as dependency inversion, are necessary.

The smaller scale effects of large-scale code organization are not immediate. However, problems that mightarise from improper organization can be crippling at later stages of a project. That is why it is recommend-able to think about project organization from its outset.

By finishing this chapter, you have covered most of traditional refactoring techniques. You have seen howcode can be reorganized from method to namespace level. You have seen a number of ailments that cantrouble your code and the ways they can be remedied. In the next chapter, you will go beyond the obvious.You will see how sometimes design can be problematic and “smelly” at first sight, but is actually an elegantsolution to a specific problem when analyzed in depth. The next chapter talks about design patterns.

404

Part IV: Advanced Refactorings

79796c13.qxd:WroxPro 2/22/08 6:30 PM Page 404

Page 440: Professional Refactoring in Visual Basic

Part V: Refactoring Applied

You have come to the final part of the book. If you have adopted all refactoring skillsdescribed so far, you can consider yourself a programmer seasoned in refactoringtechniques. However, you do have a few more important things about refactoring thatyou can learn.

In Part V you will learn things that go beyond basic object-oriented skills. You willlearn about the Holy Grail of object-oriented design — design patterns. You will alsosee how you can refactor your code having patterns as a goal that you would like toreach with your design.

As you continue in this part, you will gain a complete perspective on the history ofdevelopment in Visual Basic. You will see what the future of Visual Basic brings inthe form of Language-Integrated Query (LINQ) and other language enhancementsscheduled for the 9.0 version of Visual Basic language specification (that ships withVisual Studio 2008 and is not yet released at the time of writing).

Finally, you will take a look at the past of Visual Basic. I’ll talk about .NET versions of Visual Basic, especially Visual Basic 6.0 that helped propel Visual Basic to becomeone of the most popular development tools around. You will see how all VB 6 code isnot completely lost for the future and how refactoring can help you give a new .NETlife to your VB 6 code.

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 405

Page 441: Professional Refactoring in Visual Basic

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 406

Page 442: Professional Refactoring in Visual Basic

Refactoring to Patterns

Until now, I have promoted a rather simple approach to refactoring. You inspect the code, find thesmells, and then refactor it so the smells are eliminated or at least reduced. This approach focusesessentially on negative aspects of your code in an attempt to eradicate them. Many times, followingsuch an approach, you can reach an overall improved and even sophisticated design of your code.

It is also possible to lead your design toward some thought-out design goal. Often, such a solutionis not immediately obvious nor the most simple. However, the benefits such premeditated designcan bring often justify the added complexity.

To be able to lead your code toward some well-known design, you need to study and understanddifferent design solutions. Design patterns were created with exactly that purpose, to share anddisseminate knowledge on successful object-oriented designs that go beyond common and thatcapture the brilliance of sophisticated object-oriented solutions.

In this chapter you will:

❑ Learn what design patterns are and how to make use of them.

❑ Examine an example of a design pattern.

❑ Learn about the very influential Dependency Injection pattern.

❑ Apply refactoring to patterns concepts to the Rent-a-Wheels application.

Refactoring to Patterns chapter caveat: Design patterns are a huge subject, and there is only somuch that can be accomplished in the space of one chapter. More than to give you any practicalknowledge on design patterns and refactoring combination, the purpose of this chapter is to insti-gate you in the right direction, to interest you in the subject, so you would learn more about designpatterns and their application. As you learn more about patterns, you will recognize situationswhere they can be applied, and you will devise refactorings that will help you incorporate theminto real-life, operational code.

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 407

Page 443: Professional Refactoring in Visual Basic

Design Patterns: What’s All the Fuss About?Design patterns came into prominence in the software industry with the publication of Design Patterns:Elements of Reusable Software Design by Erich Gamma, Richard Helm, Ralph Johnson, and John M. Vlissides(Addison-Wesley, 2004), close to some 15 years ago. Since then, they have been the subject of numerousbooks, articles, conference speeches, and lectures and have become part of mainstream object-orientedcompendium.

Today, you will often hear developers discuss design not in terms of originality of the solution, but in termsof design patterns — what design patterns they used in their design and to which purpose. Patterns havebecome not only part of the common corpus of knowledge, but also part of the programmer’s commonvocabulary.

So just what are design patterns? Consider this example: a master chess player is capable of looking manymoves ahead. To some extent, this is because of his experience and his natural talent. In great part, however,this is thanks to the study of other players’ moves. Once notable moves are played out in a tournament, theyare analyzed and archived, and they become part of global chess-playing knowledge. By studying alreadyplayed out moves, learning openings and endgames, and studying common approaches in the middlegame, a master player is capable of taking advantage of other people’s inventiveness and hard work. Suchstudy gives a player a much broader horizon, meaning that player is a lot less likely to be surprised or facedwith a totally new situation. Study will liberate that player from solving some basic problems at each gameand with each move. Instead of thinking two or three moves in advance, a player can think of possibledevelopments five or six moves ahead. Even an extremely talented autodidactic player, when confrontedwith a learned player, will have little chance of success.

Design patterns will give you similar insight in the area of software development. By learning designpatterns, you will be able to meet the best developers in the world and learn from them. Programming is often viewed as a craft. In any craft, a master-apprentice relationship is very important for the learningprocess. Through design patterns you can record and learn a good portion of an expert programmer’smastery. Some crafts are not easily described or translated into the written form. Programming, on theother hand, is different because its final product is source code in a written form, and therefore can besuccessfully recorded in writing. If you‘re not able to learn from a teacher in flesh and blood, then thatteacher’s wisdom recorded in writing is the next best thing.

Patterns record moments of a programmer’s ingenuity, and they help share these moments with you.When you study patterns, you will see that there is always a nontrivial, inventive, and imaginativeturnaround view of the problem.

Defining Design PatternsPatterns capture and share the experience of others. They are a guide or template for a solution to a certainproblem in software design. They describe collaborating objects and classes that have to be adapted to dealwith certain problems, their structure, roles, and responsibilities. Patterns abstract and identify key aspectsof a design used to solve a single problem in a particular context.

While they provide you with an immediate insight on how to deal with a certain problem, you still haveto adapt the solution to your concrete situation, choose the correct variation, and often compare andweigh out different solutions for the same problem.

408

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 408

Page 444: Professional Refactoring in Visual Basic

Although patterns often include sample code, they are not intended to be reused directly, on code level, asfor example, components, frameworks, or reusable classes are. Instead of code reuse, they promote designreuse. By learning patterns, you learn to analyze, identify, and divide the problem; you learn different tech-niques to use, structure, and organize classes and objects. You compare patterns and their trade-offs andcome to understand how they relate to each other.

While some patterns can be turned into reusable components, insisting on code reuse with patterns goesin a certain sense against pattern philosophy. Learning patterns means that you should spend time ana-lyzing, understanding, assimilating, and applying the pattern. This way, you improve your design andprogramming skills. By using ready-made pattern components, you might miss important aspects ofobject-oriented design. In this case, exposure to detail is desired, and encapsulation will not help youhone your design skills.

Classifying PatternsHaving in mind the construction of a design patterns catalog that could be easily searched and browsed, the authors of Design Patterns: Elements of Reusable Software Design proposed the following classification for design patterns — all patterns should be categorized according to their purpose. “Gang of Four (GOF)”design patterns, named so because of the four authors of Design Patterns, are general-purpose patterns thatcan be further classified into three groups:

❑ Creational — These patterns solve different problems related to object creation or instantiation.

❑ Behavioral — These patterns deal with the ways that objects can communicate and shareresponsibilities in order to reach a certain goal.

❑ Structural — These patterns describe assembly and composition of objects and classes used tosolve certain problems.

Patterns can also describe designs related to some more specific field, so you can have real-time patterns,concurrency patterns, object-relational patterns, and so on. This chapter will stick with classic, mostlycreational patterns.

Pattern ElementsIn order for patterns to be efficiently consulted and used, they should be written in concise and consis-tent form. The authors of Design Patterns used a template where each pattern definition consisted of anumber of sections. This is today widely accepted form to describe a pattern. Whatever the form used to describe the pattern, it should explain the four following essential elements of each pattern:

❑ Name — A descriptive and original name is important for pattern recognition and memoriza-tion. As you study patterns, you will notice that name is associated with the solution that pat-tern provides.

Definition: Design patterns represent a proven solution to a recurring problem in agiven context.

409

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 409

Page 445: Professional Refactoring in Visual Basic

❑ Problem — The problem section describes the situation where it makes sense to use the pattern.It describes context, conditions that need to be met in order to use the pattern, and the designproblem that the pattern solves. It should also mention some examples of poor design that thispattern resolves.

❑ Solution — In this section principal classes and objects representing the design are describedtogether with their responsibilities and collaboration. This section generally includes a UMLdiagram, most often the one depicting the static structure of the solution. It should also includesome sample code that illustrates the design.

❑ Consequences — Here different trade-offs that result from pattern usage are discussed. Often,this section mentions some real-life situations where this pattern can be applied. Different patterns are compared and confronted, and cases when some patterns can be used jointly are described.

Weighing the Benefits of Design PatternsWhen you design software, you have to weigh the trade-offs between different solutions. You generallystart the project by thinking about putting data and behavior together and encapsulating it by writingclasses. You have to consider granularity and work on eliminating duplication. As the project grows andprogresses, your focus changes, and you start thinking more about flexibility, performance, and modularity.With time, more importance is placed on aspects of growth, evolution, dependencies, and maintainability.Often, your focus is dedicated to only certain aspects of the design, those more prominent at certain stagesof the project. You are not always able to envisage the future consequences because this is complex, andyou lack the necessary experience.

You often hear that decisions you make early on in the project are the ones that have the greatest conse-quences and are hardest to change. This means that you should take into consideration all of these aspectsof your design from the beginning. This does not mean that you need to start your project with somesophisticated design up front, but you need to be aware of trade-offs that different designs bring into your design.

Design patterns help you understand these trade-offs and show you how to solve certain problems.They let you compare alternatives and understand different aspects right from the start; they give you the necessary perspective that is impossible to have without the experience and shared, incremen-tal knowledge.

Using PatternsWhen you learn patterns, you are not supposed to reproduce the solution you read in the exact form itwas described. You have to choose what is best for your concrete situation. Patterns can especially helpyou with the design of those classes that do not have direct correspondence in the real world, makingyour design more flexible and reusable. These classes are always present as your design gets moresophisticated. As you assimilate patterns, you learn how to incorporate them in your design, how tochoose between different alternatives, and how to combine and adapt the patterns until you reach asophisticated design that has its own aesthetics and style.

Now that you have some grounding in what design patterns are, it’s time to turn to an example.

410

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 410

Page 446: Professional Refactoring in Visual Basic

Example Design Pattern: Abstract FactoryAs this book demonstrates, I am a great proponent of evolutionary design. If you already have a lot ofexperience with a certain type of application, you may rely on your experience to start with some moresophisticated design up front. If not, it is best if you refine your design continuously. As you work on theproject, you are able to put your design to test in a real-life situation and to adapt it to resolve unforeseenproblems that might arise.

This example of a design pattern illustrates this approach. Imagine you have to construct a very simple data maintenance application. With time, new requirements are added, and the code is growing in quantity.In order to deal with increased complexity, the code has to be refactored and design patterns applied.

The merits of different refactorings including pattern incorporation are discussed as the example goesalong. This way, you can see when the circumstances to introduce a pattern in your code are indicatedand how the pattern introduction step is always motivated by a specific need in your design. You willhave the chance to discuss and evaluate the pattern not in some abstract circumstances but with a con-crete problem at hand.

NameAbstract Factory

ProblemIn this example, you will see how Abstract Factory is applied to resolve the problem of instantiation of related data provider objects such as connection, command, adapter, and others in a consistent poly-morphic manner:

❑ The client code should not depend on specific types belonging to a provider like Oracle orMicrosoft SQL Server. In case a new database needs to be added to the list of supported data-bases, a new provider should be incorporated without requiring code modification in the client.

❑ The provider object should be instantiated in a consistent manner. If a client is using an objectfrom one provider, then there should be some mechanism put to practice so that only objectsbelonging to a specific provider are instantiated. If a client is using System.Data.SqlClient.SqlConnection, then System.Data.SqlClient.SqlCommand should be created when the client asks for a command object, and not some other command like System.Data.OracleClient.OracleConnection or another.

Data Entry Sample ApplicationThe Initial version of the application is very simple. You have a few forms that let you display and mod-ify data in a database, and they have typical Load, New, Save, and Delete buttons. The module Globalsholds the global ConnectionString variable. The database in question is the Microsoft SQL database,so the data provider from System.Data.SqlClient namespace is used to query the database. The codebehind the Delete button click event is presented in Listing 14-1.

411

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 411

Page 447: Professional Refactoring in Visual Basic

Listing 14-1: Simple Event-Handling Routine That Deletes a Record in the Database

Private Sub Delete_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Delete.Click

Dim connection As New SqlConnection(ConnectionString)Dim command As New SqlCommand( _“Delete [User] where [Id] = @Id”, connection)command.CommandType = CommandType.Textconnection.Open()Dim idParameter As SqlParameter = command.CreateParameteridParameter.ParameterName = “@Id”idParameter.Value = CInt(Me.Id.Text)idParameter.DbType = DbType.Int16command.Parameters.Add(idParameter)command.ExecuteNonQuery()connection.Close()

End Sub

This code represents a very simple approach that works for the time being. As a matter of fact, other routinesare very similar to Delete_Click, but because variables are initialized at the same line that declares them,the code looks more compact and duplicated code does not look that worrisome at the moment.

Adding Multiple Database Engine Support by Means of PolymorphismA few finished forms later, you are informed by your client that some installations use the same databasestructure, but different database engine. This client would like to use the same application to access thedata residing on Oracle and in some cases even Microsoft Access databases.

Your initial idea is to add Select...Case statements that will deal with this situation. You have added aglobal flag DbProvider declared in the Globals module that can indicate the current provider. The sameDelete_Click routine programmed this way would then look something like the code in Listing 14-2.

Listing 14-2: Delete Routine with Different Case for Different Database Provider

Private Sub Delete_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Delete.Click

Select Case Globals.DbProviderCase DbProviders.MsSql

Dim connection As New SqlConnection(ConnectionString)Dim command As New SqlCommand( _“Delete [User] where [Id] = @Id”, connection)command.CommandType = CommandType.Textconnection.Open()Dim idParameter As SqlParameter = command.CreateParameteridParameter.ParameterName = “@Id”idParameter.Value = CInt(Me.Id.Text)idParameter.DbType = DbType.Int16command.Parameters.Add(idParameter)command.ExecuteNonQuery()connection.Close()

412

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 412

Page 448: Professional Refactoring in Visual Basic

Listing 14-2: Delete Routine with Different Case for Different Database Provider (continued)

Case DbProviders.OracleDim connection As New OracleConnection(ConnectionString)Dim command As New OracleCommand( _“Delete [User] where [Id] = @Id”, connection)command.CommandType = CommandType.Textconnection.Open()Dim idParameter As OracleParameter = command.CreateParameteridParameter.ParameterName = “@Id”idParameter.Value = CInt(Me.Id.Text)idParameter.DbType = DbType.Int16command.Parameters.Add(idParameter)command.ExecuteNonQuery()connection.Close()‘...

End SelectEnd Sub

It is immediately obvious that this approach will not get you very far. The code will multiply for eachprovider you might add. So is there another approach you might use?

If you analyze the code a bit you see that the connection, command, and parameter declaration and thecreation code are the only pieces of code that differ between the two Case blocks. This is a good indica-tion for the direction in which you should start looking for a better solution.

If you take a look at SqlConnection and OracleConnection in ObjectBrowser, you notice that theyboth implement a common IDbConnection interface. They have a common parent interface, and if vari-able type is declared correctly, they can be treated as the same type of object.

The example routines employ primarily those members of SqlConnection and OracleConnection thatare defined in IDbConnection interface. The situation is similar with SqlCommand and OracleCommand;they share a common IDbCommand interface. And finally, OracleParameter and SqlParameter share acommon IDbParameter interface.

Now, if you treat your objects in polymorphic manner and declare connection as IDbConnection, command as IDbCommand, and parameter as IDbParameter, the only difference between the two caseblocks is the object creation code, namely the New statement.

Upcasting Object DeclarationUpcasting object declaration means that you change the declaration so the object is declared as some othertype higher in the type hierarchy. You change local variable declaration so that variables are declared as aninterface from the System.Data namespace. Connection is declared as System.Data.IDbConnection,command as System.Data.IDbCommand, and parameter as System.Data.IDbDataParameter. In otherroutines, where some other objects such as reader or adapter are used, a similar procedure is followed. Thecode resulting from upcasting variables can be seen in Listing 14-3.

413

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 413

Page 449: Professional Refactoring in Visual Basic

Listing 14-3: Variables Upcast to an Interface Common to All Data Providers

Private Sub Delete_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Delete.Click

Dim connection As IDbConnection = _New SqlConnection(ConnectionString)Dim command As IDbCommand = _New SqlCommand(“Delete [User] where [Id] = @Id”)command.Connection = connectioncommand.CommandType = CommandType.Textconnection.Open()Dim idParameter As IDataParameter = command.CreateParameteridParameter.ParameterName = “@Id”idParameter.Value = CInt(Me.Id.Text)idParameter.DbType = DbType.Int16command.Parameters.Add(idParameter)command.ExecuteNonQuery()connection.Close()

End Sub

Splitting Initialization from Declaration of Data Provider ObjectAt this point, you need to isolate the lines that differ depending on which data provider is used. Theonly code that differs in this example depending on the provider are the provider object instantiationstatements: New SqlConnection, New SqlCommand, and so on. Because the data provider objects are at the moment initialized on the same line that is used to declare them, in order to isolate this code, it isbest if you perform Split Initialization from Declaration refactoring on these variables. Take a look at theresulting code in Listing 14-4.

Listing 14-4: Initialization Split from Declaration for Data Provider Objects

Private Sub Delete_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Delete.Click

Dim connection As IDbConnectionconnection = New SqlConnection(ConnectionString)Dim command As IDbCommandcommand = New SqlCommand(“Delete [User] where [Id] = @Id”)command.Connection = connectioncommand.CommandType = CommandType.Textconnection.Open()Dim idParameter As IDataParameter = command.CreateParameteridParameter.ParameterName = “@Id”idParameter.Value = CInt(Me.Id.Text)idParameter.DbType = DbType.Int16command.Parameters.Add(idParameter)command.ExecuteNonQuery()connection.Close()

End Sub

Extracting Data Provider Object Creation Code as MethodsIf you were to introduce conditional code that creates different provider objects depending on specificproviders, the Select-Case block would be significantly smaller than the first intent in Listing 14-2.

414

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 414

Page 450: Professional Refactoring in Visual Basic

However, as provider creation code is common to a majority of routines in the form, the better solutionis to extract these statements as separate methods.

You can extract connection and command object creation code to separate methods. Once that is done,all of the routines on the form can reference these extracted methods. The code resulting from dataprovider objects creation methods extraction is shown in Listing 14-5.

Listing 14-5: CreateConnection and CreateCommand Methods Extracted

Public Class UserMaintenancePrivate Const DeleteUserSql As String = _“Delete [User] where [Id] = @Id”

Private Function CreateConnection() As IDbConnectionDim connection As SqlConnectionconnection = New SqlConnection()Return connection

End Function

Private Function CreateCommand() As IDbCommandDim command As SqlCommandcommand = New SqlCommand()Return command

End Function

Private Sub Delete_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles Delete.Click

Dim connection As IDbConnectionconnection = CreateConnection()connection.ConnectionString = ConnectionStringDim command As IDbCommandcommand = CreateCommand()command.CommandText = DeleteUserSqlcommand.Connection = connectioncommand.CommandType = CommandType.Textconnection.Open()Dim idParameter As IDataParameter = command.CreateParameteridParameter.ParameterName = “@Id”idParameter.Value = CInt(Me.Id.Text)idParameter.DbType = DbType.Int16command.Parameters.Add(idParameter)command.ExecuteNonQuery()connection.Close()

End Sub ‘...End Class

Introducing Provider Objects Creation LogicNow that you have methods containing purely object creation code, you can finally introduce logicthat instantiates a specific provider’s objects depending on the currently configured data provider. Theprovider name is read from the application configuration file and then appropriate variables set in themodule Globals, declaring some global variables. The configuration section in app.config file lookslike the file shown in Listing 14-6.

415

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 415

Page 451: Professional Refactoring in Visual Basic

Listing 14-6: ConnectionString Section in app.config Configuration File

<?xml version=”1.0” encoding=”utf-8” ?><configuration><!-- ... -->

<connectionStrings><add name=”DataEntry”

connectionString=”Data Source=localhost;Initial Catalog=Users;Integrated Security=SSPI;”providerName=”System.Data.SqlClient”/>

</connectionStrings></configuration>

Note that the connectionStrings section contains a providerName attribute, and the value of theattribute is the namespace of a specific provider.

You can use the Application Startup event to read these settings and populate variables in the Globalsmodule. The code for the Application Startup event is shown in Listing 14-7.

Listing 14-7: Application Startup Event Reading Provider Configuration Data

Namespace MyPartial Friend Class MyApplication

Private Sub MyApplication_Startup(ByVal sender As Object, _ByVal e As StartupEventArgs) Handles Me.Startup

Dim config As Configuration =ConfigurationManager.OpenExeConfiguration( _

ConfigurationUserLevel.None)Dim connectionStringsSection As ConnectionStringsSection = _config.ConnectionStringsDim connectionStringSettings As ConnectionStringSettings = _connectionStringsSection.ConnectionStrings(DbConfigurationSectionName)ConnectionString = connectionStringSettings.ConnectionStringGlobals.ProviderName = connectionStringSettings.ProviderName

End SubEnd Class

End Namespace

The code for the module Globals, used to hold the current data provider name and connection string, isshown in Listing 14-8.

Listing 14-8: Module Globals

Option Strict OnOption Explicit On

Module GlobalsPrivate ConnectionStringValue As StringPublic Const DbConfigurationSectionName As String = “DataEntry”

416

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 416

Page 452: Professional Refactoring in Visual Basic

Listing 14-8: Module Globals (continued)

Private ProviderNameValue As StringPublic Const MsSqlProviderName As String = “System.Data.SqlClient”Public Const OracleProviderName As String = “System.Data.OracleClient”Public Property ConnectionString() As String

GetReturn ConnectionStringValue

End GetSet(ByVal value As String)

ConnectionStringValue = valueEnd Set

End PropertyPublic Property ProviderName() As String

GetReturn ProviderNameValue

End GetSet(ByVal value As String)

ProviderNameValue = valueEnd Set

End PropertyEnd Module

Now that you have seen how the data provider configuration is read from the configuration file and main-tained in the module Globals, you can see the code for two data provider objects creation methods. It isshown in Listing 14-9.

Listing 14-9: CreateConnection and CreateCommand Methods Containing Conditional Logic

Private Function CreateConnection() As IDbConnectionDim connection As IDbConnection = NothingSelect Case Globals.ProviderName

Case Globals.MsSqlProviderNameconnection = New SqlConnection()

Case Globals.OracleProviderNameconnection = New OracleConnection()

End SelectReturn connection

End Function

Private Function CreateCommand() As IDbCommandDim command As IDbCommand = NothingSelect Case Globals.ProviderName

Case Globals.MsSqlProviderNamecommand = New SqlCommand()

Case Globals.OracleProviderNamecommand = New OracleCommand()

End SelectReturn command

End Function

417

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 417

Page 453: Professional Refactoring in Visual Basic

Extracting Creation Methods to a Separate ClassAs it soon becomes apparent, the same data provider objects creation code is present in more than oneform class. This means that the same code would be duplicated across numerous classes. In order toavoid duplication, you can extract the connection and command objects creation code into a separateclass, as shown in Listing 14-10. You can call this class DataProviderFactory.

Listing 14-10: A First Take at the Factory Class

Option Explicit OnOption Strict On

Imports System.DataImports System.Data.SqlClientImports System.Data.OracleClient

Public Class DataProviderFactoryPublic Function CreateConnection() As IDbConnection

Dim connection As IDbConnection = NothingSelect Case Globals.ProviderName

Case Globals.MsSqlProviderNameconnection = New SqlConnection()

Case Globals.OracleProviderNameconnection = New OracleConnection()

End SelectReturn connection

End FunctionPublic Function CreateCommand() As IDbCommand

Dim command As IDbCommand = NothingSelect Case Globals.ProviderName

Case Globals.MsSqlProviderNamecommand = New SqlCommand()

Case Globals.OracleProviderNamecommand = New OracleCommand()

End SelectReturn command

End FunctionEnd Class

Now that you have a class dedicated to data provider objects creation, you can move global variablesfrom the Globals module to this class. Now that they reside in a class and not in a module, they have tobe marked as Shared and prefixed with the class name when referenced. This way you manage to findsome context for former global variables. By moving variables to DataProviderFactory, moduleGlobals is rendered empty, so it can be deleted from the project.

Global variables are one of the earliest recorded and the most notorious code smells.

The code for DataProviderFactory after receiving variables from the Globals module is shown inListing 14-11.

Listing 14-11: DataProviderFactory with Provider-Related Variables

Option Explicit OnOption Strict On

418

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 418

Page 454: Professional Refactoring in Visual Basic

Listing 14-11: DataProviderFactory with Provider-Related Variables (continued)

Imports System.DataImports System.Data.SqlClientImports System.Data.OracleClient

Public Class DataProviderFactory

Public Const DbConfigurationSectionName As String = “DataEntry”Public Const MsSqlProviderName As String = “System.Data.SqlClient”Public Const OracleProviderName As String = “System.Data.OracleClient”

Private Shared ConnectionStringValue As StringPrivate Shared ProviderNameValue As String

Public Shared Property ConnectionString() As StringGet

Return ConnectionStringValueEnd GetSet(ByVal value As String)

ConnectionStringValue = valueEnd Set

End Property

Public Shared Property ProviderName() As StringGet

Return ProviderNameValueEnd GetSet(ByVal value As String)

ProviderNameValue = valueEnd Set

End Property

Public Function CreateConnection() As IDbConnectionDim connection As IDbConnection = NothingSelect Case ProviderName

Case MsSqlProviderNameconnection = New SqlConnection()

Case OracleProviderNameconnection = New OracleConnection()

End SelectReturn connection

End FunctionPublic Function CreateCommand() As IDbCommand

Dim command As IDbCommand = NothingSelect Case ProviderName

Case MsSqlProviderNamecommand = New SqlCommand()

Case OracleProviderNamecommand = New OracleCommand()

End SelectReturn command

End Function

End Class

419

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 419

Page 455: Professional Refactoring in Visual Basic

You now have a functional factory class. It provides methods that instantiate the correct connection orcommand object for a certain provider. However, the class still has a few major problems:

❑ If you change the ProviderName property, you will be able to instantiate objects for differentproviders from the same factory. What’s more, all instances of factory share the ProviderNameproperty, meaning that only one data provider can be current at a given time. You need to be ableto create objects in a consistent form. If the factory has returned an instance of System.Data.SqlClient.SqlConnection when calling the CreateConnection method, it is only logicalthat CreateCommand should return an instance of System.Data.SqlClient.SqlCommand andnot some other command object.

❑ As you add new providers, you have to modify the factory class, adding another Case block tothe Select-Case statement.

❑ The Select-Case statement is duplicated between different methods. While this duplication is not yet very harmful, as you add more object creation methods and more providers, it willbecome more annoying.

❑ Finally, you have single class referencing a number of providers. As providers are added, this will result in a growing Imports statement section and a class that centralizes dependencies on data providers. In order to compile the class, you must have at your disposal all the providersit references.

How should you go about addressing this? What is the next step you should take? Well, here is where theAbstract Factory pattern kicks in. It describes the solution that can be applied in the situation like this.

SolutionYou have refactored the code in incremental steps up to this point. You have managed to improve thedesign, but there are a number of issues that are still present in the code. However, the solution is notimmediately visible.

Refactoring Creational Code to Abstract Data Provider FactoryThe Abstract Factory pattern gives the solution to the problem at hand. It amounts to the following steps:

1. Create an abstract factory that defines interfaces for the creation of related objects.

2. Use concrete factories that inherit the abstract factory override and implement creational meth-ods that instantiate related concrete types defined by Abstract Factory’s abstract methods.

In this example, you will have a single factory for each data provider:

❑ For an Oracle data provider, you will create OracleProviderFactory.

❑ For a Microsoft data provider, you will create MsSqlProviderFactory.

❑ For an OLE DB provider, you will create OleDbProviderFactory.

You will add another concrete factory class for each provider you wish to support.

In this example, in order to refactor code to the Abstract Factory pattern, you perform ExtractSubclass refactoring on the DataProviderFactory class from Listing 14-11. The extracted methodsare CreateConnection and CreateCommand, and each subclass contains one Case block. Finally,

420

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 420

Page 456: Professional Refactoring in Visual Basic

you make the DataProviderFactory class abstract, and you mark CreateConnection andCreateCommand with MustOverride. These methods in DataProviderFactory contain no imple-mentation. The resulting code is presented in Listing 14-12.

Listing 14-12: DataProviderFactory as Abstract Factory

Option Explicit OnOption Strict On

Imports System.DataImports System.Data.SqlClientImports System.Data.OracleClient

Public MustInherit Class DataProviderFactory

Private Shared ConnectionStringValue As StringPublic Const DbConfigurationSectionName As String = “DataEntry”Private Shared ProviderNameValue As StringPublic Const MsSqlProviderName As String = “System.Data.SqlClient”Public Const OracleProviderName As String = “System.Data.OracleClient”Public Const OleDbProviderName As String = “System.Data.OleDb”

Public Shared Property ConnectionString() As StringGet

Return ConnectionStringValueEnd GetSet(ByVal value As String)

ConnectionStringValue = valueEnd Set

End Property

Public Shared Property ProviderName() As StringGet

Return ProviderNameValueEnd GetSet(ByVal value As String)

ProviderNameValue = valueEnd Set

End Property

Public Shared Function CreateDataProviderFactory( _ByVal provideName As String) As DataProviderFactory

Select Case provideNameCase MsSqlProviderName

Return New MsSqlProviderFactoryCase OracleProviderName

Return New OracleProviderFactoryCase OleDbProviderName

Return New OleDbProviderFactoryEnd SelectReturn Nothing

End Function

Public Shared Function CreateDataProviderFactory() As DataProviderFactory

Continued

421

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 421

Page 457: Professional Refactoring in Visual Basic

Listing 14-12: DataProviderFactory as Abstract Factory (continued)

Return CreateDataProviderFactory(ProviderName)End Function

Public MustOverride Function CreateConnection() As IDbConnection

Public MustOverride Function CreateCommand() As IDbCommand

End Class

Note how both CreateConnection and CreateCommand return objects of interface type from the rootdata provider namespace System.Data. The class does not reference any concrete providers. The sharedCreateDataProviderFactory method is used to return concrete factory and contains a single SelectCase statement. Listings 14-13 to 14-15 present the code of each concrete factory.

Listing 14-13: MsSqlProviderFactory Factory

Option Strict OnOption Explicit On

Imports System.Data.SqlClient

Public Class MsSqlProviderFactoryInherits DataProviderFactory

Public Overrides Function CreateCommand() _As System.Data.IDbCommand

Return New SqlCommandEnd Function

Public Overrides Function CreateConnection() _As System.Data.IDbConnection

Return New SqlConnectionEnd Function

End Class

Note how MsSqlProviderFactory imports only the System.Data.SqlClient namespace.

Listing 14-14: OleDbProviderFactory Factory

Option Strict OnOption Explicit On

Imports System.Data.OleDb

Public Class OleDbProviderFactoryInherits DataProviderFactory

Public Overrides Function CreateCommand() _As System.Data.IDbCommand

Return New OleDbCommand

422

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 422

Page 458: Professional Refactoring in Visual Basic

Listing 14-14: OleDbProviderFactory Factory (continued)

End Function

Public Overrides Function CreateConnection() _As System.Data.IDbConnection

Return New OleDbConnectionEnd Function

End Class

OleDbProviderFactory imports only the System.Data.OleDb namespace.

Listing 14-15: OracleProviderFactory Factory

Option Strict OnOption Explicit On

Imports System.Data.OracleClient

Public Class OracleProviderFactoryInherits DataProviderFactory

Public Overrides Function CreateCommand() _As System.Data.IDbCommand

Return New OracleCommandEnd Function

Public Overrides Function CreateConnection() _As System.Data.IDbConnection

Return New OracleConnectionEnd Function

End Class

Finally, OracleProviderFactory imports only the System.Data.OracleClient namespace. You cansee this code represented graphically in Figure 14-1.

Figure 14-1

MsSqlProviderFactory OleDbProviderFactory OracleProviderFactory

+CreateConnection()+CreateCommand()

+CreateConnection()+CreateCommand()

+CreateConnection()+CreateCommand()

+ConnectionString+ProviderName+CreateConnection()+CreateCommand()+CreateDataProviderFactory()

DataProviderFactory

423

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 423

Page 459: Professional Refactoring in Visual Basic

The future implementation and modification is easy to follow. For each new provider, a new concretefactory inheriting DataProviderFactory is added. For each new object, for example IDbDataAdapter,a new creation method (CreateDataAdapter) is added.

ConsequencesNow that you have implemented the solution, you should understand the consequences of this patternin detail. First, consider the benefits that this pattern brings to your design:

❑ Clients can use data provider in a polymorphic manner. They never need to reference any con-crete data provider class. All they need to know are interfaces from the system data namespace.This leads to less code and less duplication in your code and all other benefits that polymorphicbehavior can bring.

❑ Polymorphic use of providers makes exchanging different providers easy. Clients are neveraware of the concrete type of object that factory creates. This makes it easy to add configurationcapability to the application and to externalize to a configuration file a string that indicates thecurrent provider. This way, there is no need to change the code or to recompile the applicationin order to use different providers. This makes your code less dependent on a specific databaseand helps evade vendor lock-in.

❑ The rule where only related objects are created is enforced. Once you create a factory, itreturns provider objects of a specific provider only. MsSqlProviderFactory always createsobjects from System.Data.SqlClient, SqlConnection, and SqlCommand and none other.This makes creational code behave in a consistent and predictive manner.

❑ From a testing perspective, the pattern makes client code easier to unit test. All you need to do when executing tests is use MockDataProviderFactory, a factory that creates mocked dataprovider objects.

There are also some negative sides to this pattern. You need to understand those as well.

❑ Creating another type that needs to be created by factory is not easy. For example, if you wishto add a creational method for Data Adapter, you need to add a new method to the abstractDataProviderFactory class. This, as a consequence, obliges you to implement this method in all concrete factory classes inheriting the DataProviderFactory class.

❑ Adding a new family of objects (new data provider in this example) is relatively costly,because it requires you to implement new a concrete factory class for each family you add.

When you need to use the pattern, it does not mean that the design is cast in stone and that you have to follow the code from the pattern example line by line. You can modify and adapt the pattern so it suitsyour own needs. As a matter of fact, that is the only correct way to use patterns. To illustrate that, take alook at some variations that can be applied to the Abstract Factory pattern.

VariationsThere are different ways to refine and implement this pattern. The following sections explore some ofthe variations you might use when implementing the pattern yourself.

424

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 424

Page 460: Professional Refactoring in Visual Basic

Instantiating the Factory ItselfMaybe the biggest problem with the solution is the concrete factory creation code from Listing 14-12. If you take a look at the shared CreateDataProviderFactory method, you can see that you need tomodify the code of Abstract Factory by adding a new Case block for each new concrete factory thatneeds to be incorporated. One solution is to use the concrete factory’s name as a provider parameter(ProviderName property) and then instantiate the factory by means of reflection. This way, each time anew concrete factory is added, it is enough to give the correct provider name parameter to instantiate thecorrect provider, and no modification to DataProviderFactory is required. If you wish, you can give ita try and implement CreateDataProviderFactory in this fashion.

Default ImplementationIn some cases, it might be applicable to have some default implementation for the factory. In that case,you could instantiate the AbstractFactory itself that would provide some meaningful default imple-mentation of the factory. Needless to say, this would convert the abstract factory into a concrete factoryitself. It means you would have to erase the MustInherit keyword from the abstract factory’s declara-tion and change the MustOverride keyword to Overridable in the creation methods declaration andprovide default implementation for them. In this case, the OLE DB provider makes the most sense as adefault implementation because of its versatility in connecting to different data sources (provided thatnecessary dll libraries are available).

Concrete Factories as SingletonsA singleton is another design pattern. It helps you enforce only a single instance of a certain type ifyour application is created during application lifetime. It makes sense to implement concrete factoriesas singletons, because only a single instance of each concrete factory will be needed anyway.

Factory Inheritance HierarchiesIn a similar way you create hierarchies of other objects, you can have hierarchies of factories. Imagine you have programmed an enhanced SQL Command object, capable of caching data parameters. TheEnhancedSqlCommand class works with the same System.Data.SqlClient.SqlConnection object asSystem.Data.SqlClient.SqlCommand does. You can program your EnhancedMsSqlProviderFactoryinheriting MsSqlProviderFactory and override a single CreateCommand method that will return aninstance of EnhancedSqlCommand instead of the standard SqlCommand.

Data Provider Factory in .NET Framework 2.0If you are using Visual Studio 2005 or later, you do not need to create your own data provider facto-ries. Creators of the .NET Framework have realized the benefits of the Abstract Factory pattern andprovided the classes that implement it. Take a look at System.Data.Common.DbProviderFactoryand System.Data.Common.DbProviderFactories classes and compare their design to one used inthis chapter.

The existence of these classes by no means renders our refactor to the abstract factory exercise futile. Thepurpose of this example was not to solve a specific problem but to use a well-known problem to exposea possible solution to this and similar problems; to demonstrate the effectiveness of the refactoringprocess in combination with design patterns. Now you have tasted the power of design patterns andrefactoring combination. The next section continues this discussion with a few points about a relatedand very influential pattern.

425

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 425

Page 461: Professional Refactoring in Visual Basic

Dependency Injection PatternWhile not one of classic GOF patterns, the Dependency Injection (DI) pattern has become hugely influ-ential as of late. It has given rise to numerous lightweight containers and unobtrusive DI frameworks. It has changed the way many developers approach application design. However, I must warn you rightaway that improvements that this approach brings to your design and especially to your developmentprocess are not easily observable on a small scale. Nevertheless, it is important to learn more about thispattern and see what benefits it brings.

ProblemWhen you write your typical industrial-strength application, you are faced with great complexity thattypical real-life software projects are characterized by. The common way to deal with the complexity inyour project is modularity. Instead of making your application as a single monolithic construct, youassemble your application out of existing or newly created components and services. Each componentand service has to solve a piece of the puzzle that your application represents.

Software Components and ServicesTo put it in simple terms, components are reusable and deployable binaries created to bereused as a part of some (often unknown) application. Component-based applicationsare created by composing or assembling different components. As a VB programmer,you are most probably familiar with COM and COM + components. (Software compo-nents are discussed in Chapter 8.)

Services are similar to components and they are also used by other applications. Serviceshave the capacity to be accessed remotely, meaning that they can be hosted by someremote machine and generally execute in some other process. A typical example of serviceis a web service that is accessed through SOAP protocol.

Providing components with remote access can often be technically straightforward, sothe difference between the two can be rather blurred. It may depend only on how youdeploy the component. Finally, in a .NET environment, you access and use servicesand components in the same way you would use and access any other object.

Dependency Injection and Inversion of ControlDI is also often referred to by another name: Inversion of Control (IoC). These terms areused more or less interchangeably throughout the chapter. However, Inversion of Controlis a more general technique often used in programming when part of control is resignedin favor of some other entity. For example, when you code the Windows Form class, youimplement different event handlers that react to user actions. In this case, you have nocontrol over the flow of the program; it is the user that can choose to push any enabledbutton at any point in time. On the other hand, had you programmed this as a consoleapplication, asking the user to enter one data at a time, you would interact with the userin a strictly defined order of execution that is under your complete control.

426

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 426

Page 462: Professional Refactoring in Visual Basic

This modular approach is not without its own problems. These problems become more relevant with thescale and overall complexity of the application. Some of the problems you have to deal with are:

❑ How to create components and locate services

❑ How to deal with dependencies between your components and services

❑ How to avoid wedding yourself into single implementation of the component or the service

❑ How to test such an application easily

❑ How to configure the application in a uniform and consistent manner

❑ How to provide the application with additional services such as transaction support or objectpooling without obliging components to implement specific interfaces or inherit specific classes

In any assembled application, you have two principal collaborators:

❑ Client

❑ Service

A Client collaborator (often a service or component in its own right) asks a Service collaborator to providesome service. You can see this represented graphically on Figure 14-2.

Figure 14-2

In order to make this more illustrative, imagine that the client is an Online Purchase component and theservice is a Credit Card Verification service.

The first problem the client has to resolve is to get a hold of service. In a normal object-oriented scenario,the Online Purchase component would simply create a new instance of the Credit Card Verification serviceobject. This is as simple as this:

Dim verificationService as CreditCardVerificationService _= New CreditCardVerificationService())

The problems with this approach are numerous. Because you cannot easily switch to another a CreditCard Verification service implementation:

❑ You depend on a specific service implementation and service provider. What happens if thereis another verification service that charges less?

❑ Your application is not easy to test. Such services generally charge for use, so you wouldn’t liketo use a real service just so you could test your application.

❑ You are not isolated from changes to the verification service, because you depend upon itsimplementation.

perform serviceClient Service

427

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 427

Page 463: Professional Refactoring in Visual Basic

You can generally distinguish between a service implementation and a service interface. So you couldreduce the dependency on the service by viewing the service through its interface, like this:

Dim verificationService as CreditCardVerificationService _= New PremiumCreditCardVerificationService ())

However, while dependency is reduced to a single service creation line, the Online Purchase componentstill depends on a specific Credit Card Verification Service implementation.

Earlier in this chapter, you saw how you can use factory classes to decouple the client from a specificclass implementation. In the case of services, instead of factories, you rely on Service Locator classes toprovide you with services you need to use. Online Purchase could use a Service Locator to obtain aninstance of Credit Card Verification Service:

Dim locator As ServiceLocator = New ServiceLocatorDim verificationService As CreditCardVerificationService _= locator.find(“premum”)

Or, to present it graphically in more general terms, using Service and Client generic names for collabora-tors, take a look at Figure 14-3.

Figure 14-3

So, how does this fare compared to previous solutions? Obviously, this is much better, because you donot depend on a specific service implementation. There is one problem, however; the client still dependson the Service Locator. You have to ask (send message to) the locator that you need to find a certainobject (service).

Depending on the Service Locator, this can be a limitation, but is not necessarily excessively damaging.There is a way, however, to do even without Service Locator. The solution is to have someone else injecta service inside the client. I will call such an entity an assembler. When creating clients, an assemblerinjects the client with specific service implementation.

SolutionWhen you program your code following the Dependency Injection pattern, you pursue the followingparadigm shift, expressed here from the client’s point of view: “Why should I create or locate compo-nents or services or even ask someone else to create or locate them for me? I’ll just declare that I needcomponents or services, and I’ll let someone else provide them for me.”

perform serviceClient

ServiceLocator ServiceImplementation

«interface»IService

locate

428

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 428

Page 464: Professional Refactoring in Visual Basic

Here is where inversion of control comes from: the client is letting someone else create or locate compo-nents or services the client itself needs. Here is what this would look like presented graphically (take alook at Figure 14-4).

Figure 14-4

You can observe one very important effect. The client now depends only on the IService interface. Itmeans that you have successfully resolved the service implementation location problem in such a waythat your client does not depend even on a Service Locator anymore.

You will note that the Assembler, however, depends both on client and service implementation. You mightthink at first that all you have accomplished is to move dependencies from one place to another. You willprobably have numerous components, services, and clients in each application, so having a single place toreconfigure dependencies is already an important accomplishment. Also, this way you are managing tocreate reusable components that do not depend on specific context. If you consider that client can also be acomponent, you have managed to reduce its dependencies on specific service implementations, factories,or service locators. Finally, Assembler dependencies can be often removed by programmatic methods likereflection. This is often the case with lightweight containers.

Constructor-Based vs. Property-Based InjectionIn order for DI to work, the client needs a mechanism that can be used to inject dependencies. For this,we can count on standard object-oriented mechanisms, namely constructors and properties.

❑ If the client declares a service as a property, all an assembler has to do is set the property to refer-ence a service implementation instance. For example, an assembler can contain the following code:

Dim purchase As OnlinePurchase = New OnlinePurchasepurchase.VerificationService = New PremiumCreditCardVerificationService

❑ If the client declares a service as a constructor parameter, all the assembler has to do is create theclient invoking this parameterized constructor and passing a service implementation instance asa parameter.

Dim purchase As OnlinePurchase = _New OnlinePurchase(New PremiumCreditCardVerificationService)

Which way is better? There have been a lot of discussions on the subject. The advantage of constructor-basedinjection is that it instantiates objects in a legal state, with all dependencies provided in the constructor itself.The advantage of property-based injection is that it allows clients to have a default (nonparameterized)

perform serviceClient

Assembler ServiceImplementation

«interface»IService

create or locate

inject service

429

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 429

Page 465: Professional Refactoring in Visual Basic

constructor, a requirement of some technologies. For example, if you wish to program COM components inVB, you must provide public default, nonparameterized constructors. Finally, there are other forms of injec-tion, for example, interface-based. The choice is more or less the question of specific context and maybe even personal preferences, so I will not dwell on the subject. Now that you can provide the client with thedependency, the question is how the assembler knows what concrete service implementation it is supposedto inject?

What Service Implementation to InjectVery often, you have at your disposal more than one implementation of a service or component. So, howdoes the assembler know which concrete implementation should be injected into the client?

Coded AssemblerIn its most simple form, an assembler can contain literal code that creates and injects concrete dependenciesinto the client. Go back to the Online Purchase component and Credit Card Verification Service example. In Listing 14-16 you can see an example of assembler-encoded injection.

Listing 14-16: Assembler-Encoded Injection

Option Strict OnOption Explicit On

Public Class AssemblerPublic Sub IntializeApplication()

Dim purchase As OnlinePurchase = _New OnlinePurchase(New PremiumCreditCardVerificationService)‘...set purchase client with instance of purchase

End Sub‘...

End Class

What happens is that when you construct applications, you generally end up with large object graphs. In this example, this means that the instance of Online Purchase, the client to the Credit Card VerificationService, will probably be a component that is required by its own client, and so on.

Autowiring AssemblerSuch an assembler is used in combination with other assembling approaches. The most basic way for acomponent to declare a certain dependency and that it should be injected with a service is on the codelevel. If a client has declared a writable property or constructor parameter of a certain type, it means itdepends on this type and needs to have an instance of that type injected. In case there is only single classavailable at runtime that implements a certain service interface, it is obvious that this class is exactly theimplementation that client needs because, simply put, there is no other.

In such a case, many DI-based frameworks are capable of discovering and providing the client with theonly available implementation because they are capable of inspecting components through introspection.This way, some dependencies can be resolved with no additional effort. This is an important characteristicof full-blown assemblers because it keeps applications simple. At the same time it permits programmingto an abstraction principle to be followed, without incurring additional configuration cost.

430

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 430

Page 466: Professional Refactoring in Visual Basic

Metadata AssemblerThis form of dependency resolution is the most common in full-blown DI frameworks. They let you exter-nalize to a configuration file (often XML) the dependency information. Such an assembler is capable ofresolving complex dependency graphs and lets you mix standard configuration data with dependencyinformation. Take a look at sample configuration file used by the Spring Framework for .NET:

<object id=”onlinePurchase” type=”OnlinePurchase, OnlinePurchaseAssembly “><property name=” verificationService” ref=”premiumVerification”/>

</object>

<object id=”premiumVerification” type=”PremiumCreditCardVerificationService,CreditCardVerificationAssembly”/>

You can see how each object is defined with a unique ID and type that consists of a full class name andassembly name. In this case, onlinePurchase is property-injected with the premiumVerificationobject, an instance of the PremiumCreditCardVerificationService class.

An alternative to the configuration files is attributes. This is a bit more an intrusive technique (you haveto recompile your code in order to change the way the application is assembled) and is supported byNano Container for .NET. For example:

<RegisterWithContainer()> _Public Class PremiumCreditCardVerificationService‘...

The RegisterWithContainer attribute registers PremiumCreditCardVerificationServicewith the assembler under a class name. The assembler is capable of discovering thatPremiumCreditCardVerificationService is a type of CreditCardVerificationService.

ConsequencesYou can obtain numerous benefits by following Dependency Injection pattern, and the recent popularityof DI frameworks and lightweight containers confirms it. For example:

❑ Solution to component creation and service location code — You are liberated from coding different factories or programming to different service locators. This is not the concern of yourcomponent or service any more. (Remember, a client of some service or component is most oftena service or component itself).

❑ Decoupling of client from specific implementation — By not depending on a specific imple-mentation, you are harvesting all the benefits of the polymorphic approach, and you are creatinghighly reusable, configurable components. Such components do not depend on a specific contextto be reused.

❑ Ease of configuration — Your application is glued from loosely coupled elements. You canexchange different implementations with ease, often just by changing the configuration file. The radically different approach to configuration, completely in the spirit of object-oriented programming, saves you from programming configuration plumbing code. Through DI, config-uration elements are just objects that you assign to other objects.

431

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 431

Page 467: Professional Refactoring in Visual Basic

❑ Ease of testing — You can easily exchange real implementation with stubs or mocked imple-mentation of services and components for testing purposes, facilitating different forms of testingwithout incurring any programming cost.

❑ DI-based lightweight containers make it easy to add support for other, crosscutting services —An example of this is transaction or pooling that can be added declaratively, without imposingany limitation on the component itself.

❑ DI containers are unobtrusive — This means you do not have to extend a class or implement aspecific interface in order to use services they provide. Often, your class is not aware of the con-tainer’s existence. This is not the case with heavyweight containers.

Some of the negative sides of DI pattern are that you create code that at the first sight is not easy to follow (Inversion of Control). Some programmers are put off because they have to write additional con-figuration files. Often, these files are larger than necessary, because programmers do not use auto-wiringcapacities present in all major DI containers.

Lightweight vs. Heavyweight ContainersDI has given rise to numerous unobtrusive frameworks and lightweight containers. Probably the mostpopular lightweights on the .NET platform are Spring .NET and the Pico/Nano .NET container. Theselightweight containers have certain advantages compared to traditional application server heavyweightcontainers like Microsoft Transaction Server (MTS).

In this case, the choice of names is probably not the best one, because the difference between two typesof containers consists of much more than simple size in bytes. The following list explores the differencesbetween the two in greater detail.

What Is a Component Container?In the simplest form, component containers are capable of hosting components and tak-ing care of their lifetime. From the client point of view, when a component is hostedinside a container, the client does not need to take care of creating and initializing thecomponent, because the container does this for the client. From a hosted componentpoint of view, the component can count on the container to provide it with some infor-mation on component lifetime. The container can notify component of some importantevents in the lifetime of component, such as creation, that can be used by the compo-nent to perform necessary initialization or destruction and can be used by componentto perform the necessary cleanup. In addition, containers can provide a host of addi-tional services to components, such as assembly and dependency resolution, configura-tion, manageability, and so on.

Definition: POCO is an acronym for Plain Ol’ CLR Object. It refers to objects thatare not forced to implement any specific interface or inherit any specific class inorder to make use of services certain frameworks or containers provide.

432

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 432

Page 468: Professional Refactoring in Visual Basic

❑ DI support — Both lightweight and heavyweight containers support DI. However, MTS supportsonly the injection of services that are part of MTS and of some simple configuration data throughthe “Object Construction” feature. Lightweight containers can host and inject any .NET object.

❑ POCO programming model — In order to host your component inside MTS, the implementingclass must inherit the System.EnterpriseServices.ServicedComponent class. BecauseVisual Basic (like the majority of .NET languages) supports only single inheritance, this imposesan important limitation to the programming model you need to follow. Also, you won’t be ableto reuse other classes through inheritance when programming your component, nor will otherclasses be able to inherit your class, unless they get tied up with MTS libraries. Lightweight con-tainers impose no such limitation. You can program your classes as simple POCOs, not tyingthem up to a specific hosting environment. Crosscutting services such as transaction support areimplemented in the form of interceptors and can be added declaratively.

❑ Modular architecture — Lightweight containers are generally programmed in modular manner,so you can use only the services you need. This means that you need to reference and distributeonly the libraries you really use. And if you really need to, you can program your own. MTS ismonolithic application and cannot be installed or distributed partially.

❑ Embedding — MTS is an application server that runs in a separate process from your ownapplication. It is registered as a Windows service and has to be installed and administered sepa-rately from your application. This can be an advantage in certain circumstances, for example, inlarge enterprises that use components produced by different software companies. However, thisalso dictates its size and complexity. Lightweight containers can be embedded and distributedas a part of your application. Core libraries are often so small that they can be embedded insidethick clients (as in a client-server model) and distributed over the Internet.

❑ Ease of testing — Thanks to DI, with lightweight containers, implementation of different servicescan be easily replaced with stubs or mocks. This facilitates the testing process. With heavyweightcontainers, this is not possible.

❑ Centralization — Centralization is characteristic of heavyweight application servers. Becausecomponents are hosted in a centralized server, this can facilitate administration, monitoring,configuration, and assembly of applications in one central location. Lightweight containers arenot installed separately and are bundled together with the application.

The differences between the two types of containers are listed in Table 14-1.

Table 14-1: Lightweight vs. Heavyweight Containers

Characteristics Lightweight Containers Heavyweight Containers

DI Yes Very Limited

POCO programming model Yes No

Modular architecture Yes No

Embedding Yes No

Ease of testing Yes No

Centralization No Yes

433

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 433

Page 469: Professional Refactoring in Visual Basic

Now that you have seen the differences between the two types of containers, it seems that lightweightwins straight out. Does this means that this is the end of the road for heavyweight containers? Well, notlikely. They are still proven technology and very useful for highly scalable, high traffic, large enterprise-level applications that need to support redundancy and high availability.

Up until now, however, you could often witness application servers used even for an application thatbenefited minimally from services they provide and where using heavyweight containers is clearlyoverkill. The advent of lightweight containers should help developers program more productively andshould help place the heavyweight containers in their rightful niche.

Refactoring to DIYou have already seen how code can be refactored to use DI in the example used to discuss DI. You haveseen how the assembler is used to inject dependency into the client instead of having the client find thedependency.

To summarize: instead of instantiating and using a factory to create the component or instead of using aservice locator to locate the service, you invert the control and let someone else inject the dependenciesyour component needs. The following section puts this into practice with the Rent-a-Wheels application.

Refactoring to Patterns and Rent-a-Wheels Application

If you consider the refactorings performed in Chapter 12, you might recall a first attempt at AbstractFactory for data provider objects in the form of AbstractDataObjectsProvider class with concreteMSSQLDataObjectsProvider factory class.

Eliminating Code That Duplicates Functionality Available in .NET Framework

Because the same functionality is provided by the DbProviderFactories and DbProviderFactoryclasses in the .NET Framework System.Data.Common namespace, you can eliminate theAbstractDataObjectsProvider and MSSQLDataObjectsProvider and use classes provided by the .NET Framework instead. The classes aren’t adding any additional value, so the less code youhave to maintain, the better. Because the DbProviderFactory class provides a comprehensive list of methods for data provider object creation, you can decouple the rest of the code Data classes(BranchData, ModelData, and so on) code from the concrete data providers.

For example the BranchData Delete method does not reference SqlCommand type anymore. Take alook at the original code:

Public Overrides Sub Delete(ByVal branch As Branch)Dim command As IDbCommand = New SqlCommandAddParameter(command, IdParamterName, DbType.Int32, _branch.Id)ExecuteNonQuery(command, DeleteBranchSql)

End Sub

434

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 434

Page 470: Professional Refactoring in Visual Basic

This code is now refactored to following form:

Public Overrides Sub Delete(ByVal branch As Branch)Dim command As IDbCommand = MyBase.DbProviderFactory.CreateCommandAddParameter(command, IdParamterName, DbType.Int32, _branch.Id)ExecuteNonQuery(command, DeleteBranchSql)

End Sub

Injecting Data Classes to GUI Classes via Dependency Injection

In order to provide less coupling between the GUI and Data layers and make the RentAWheel.Dataassembly nondependent on Rent-a-Wheels configuration specifics, you can do the following:

❑ Inject GUI classes with Data implementations, instead of having GUI classes creating instancesof Data.

❑ Inject Data classes with connection string and a concrete DbProviderFactory. This will decoupleData classes from the specific Rent-a-Wheels implementation and make especially AbstractDatahighly reusable.

Instead of incorporating third-party assemblers, you implement your own coded assembler. ApplicationStartup even provides a convenient trigger for DI, so you can code your assembler in the form of aMyApplication class. The code for the MyApplication class is available in Listing 14-17.

Listing 14-17: MyApplication Class as DI Assembler

Option Strict OnOption Explicit On

Imports System.DataImports System.Data.CommonImports System.Configuration

Imports RentAWheel.Data.Implementation

Imports Microsoft.VisualBasic.ApplicationServices

Namespace My

‘ The following events are available for MyApplication:‘...Partial Friend Class MyApplication

Private connectionStringSettings As ConnectionStringSettingsPrivate Const DbStringConfigurationSectionName As String = “RentAWheels”

Private branchData As BranchDataPrivate vehicleData As VehicleDataPrivate modelData As ModelData

Continued

435

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 435

Page 471: Professional Refactoring in Visual Basic

Listing 14-17: MyApplication Class as DI Assembler (continued)

Private categoryData As VehicleCategoryData

Private Sub MyApplication_Startup(ByVal sender As Object, _ByVal e As StartupEventArgs) Handles Me.Startup

CreateDatas()ReadConnectionSettings()AssignConnectionStringSettingsToDatas()SetUpBranchMaintenanceForm()SetUpChangeBranchForm()SetupFleetMaintenanceForm()SetUpFleetViewForm()SetupModelMaintenanceForm()SetUpRentVehicleForm()SetUpVehicleCategoriesMaintenanceForm()SetUpVehicleReceptionForm()

End SubPrivate Sub AssignConnectionStringSettingsToDatas()

branchData.ConnectionStringSettings = Me.connectionStringSettingsvehicleData.ConnectionStringSettings = Me.connectionStringSettingsmodelData.ConnectionStringSettings = Me.connectionStringSettingscategoryData.ConnectionStringSettings = Me.connectionStringSettings

End SubPrivate Sub CreateDatas()

branchData = New BranchDatavehicleData = New VehicleDatamodelData = New ModelDatacategoryData = New VehicleCategoryData

End SubPrivate Sub ReadConnectionSettings()

Dim config As Configuration = _ConfigurationManager.OpenExeConfiguration( _ConfigurationUserLevel.None)Dim connectionStringsSection As ConnectionStringsSection = _config.ConnectionStringsconnectionStringSettings = _connectionStringsSection.ConnectionStrings( _DbStringConfigurationSectionName)

End SubPrivate Sub SetupModelMaintenanceForm()

ModelMaintenance.Helper.ModelData = modelDataModelMaintenance.Helper.CategoryData = categoryData

End SubPrivate Sub SetUpFleetViewForm()

FleetView.BranchData = branchDataFleetView.CategoryData = categoryDataFleetView.VehicleData = vehicleData

End SubPrivate Sub SetupFleetMaintenanceForm()

FleetMaintenance.Helper.BranchData = branchDataFleetMaintenance.Helper.ModelData = modelData

436

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 436

Page 472: Professional Refactoring in Visual Basic

Listing 14-17: MyApplication Class as DI Assembler (continued)

FleetMaintenance.Helper.VehicleData = vehicleDataEnd SubPrivate Sub SetUpChangeBranchForm()

ChangeBranch.BranchData = branchDataChangeBranch.VehicleData = vehicleData

End SubPrivate Sub SetUpBranchMaintenanceForm()

BranchMaintenance.Helper.BranchData = branchDataEnd SubPrivate Sub SetUpVehicleReceptionForm()

VehicleReception.VehicleData = vehicleDataEnd SubPrivate Sub SetUpVehicleCategoriesMaintenanceForm()

VehicleCategoriesMaintenance.Helper.VehicleCategoryData = _categoryData

End SubPrivate Sub SetUpRentVehicleForm()

RentVehicle.VehicleData = vehicleDataEnd Sub

End Class

End Namespace

This is not yet all I have to say about design patterns and Rent-a-Wheels. It seems that I have incorpo-rated certain design patterns a lot before this chapter.

CRUD Persistence PatternIf you take a look at different Data classes, you will see that they all inherit AbstractData and imple-ment certain abstract methods that AbstractData declares:

Public MustOverride Function GetAll() As IList(Of PersistedObject)

Public MustOverride Sub Delete(ByVal persisted As PersistedObject)

Public MustOverride Sub Update(ByVal persisted As PersistedObject)

Public MustOverride Sub Insert(ByVal persisted As PersistedObject)

You can see that for each domain object, the data implements a few persistence methods. This pattern isactually not so new. It is often referred to by the acronym CRUD, meaning Create, Retrieve, Update, andDelete. The pattern is popular today and often goes in similar form and under different names such asDAO for Data Access Object and others. Mind you, this implementation here is very basic. It doesn’thave methods for parameterized querying, not to mention more advanced problems like concurrency,stale data, transactions, lazy loading, and so on. Object persistence is a very complex field and is dis-cussed in the next chapter.

437

Chapter 14: Refactoring to Patterns

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 437

Page 473: Professional Refactoring in Visual Basic

SummarySoftware design patterns provide a wealth of programming wisdom and ingenuity. Learning patterns willgreatly hone your designer skills but will not be enough for real-life pattern application. Because the onlypractical way to approach design is evolutionary, you will have to learn techniques on how to refactor yourcode so it incorporates solutions described by design patterns.

This chapter gave you a first taste of design patterns and refactoring to patterns process. When refactoringto patterns, you are not guided only by the immediate goal of eliminating undesired qualities from yourcode or smells; you are led forward by a clear vision on desired design that you wish to implement.

You have seen how the problem of creation of related objects can be resolved by an Abstract Factorypattern, decoupling the client from the knowledge of the concrete family it will use. You have seen howtypical creational code can be refactored along the lines of the Abstract Factory pattern.

This chapter also discussed Dependency Injection pattern, a design pattern that is increasingly influentialand that is changing the way typical applications are programmed and assembled. You have seen that DI can be applied in very simple form, or can be implemented with the help of DI-based containers. Youhave seen the benefit of the pattern and how containers based on DI compare to traditional heavyweightcontainers and application servers.

I hope that after this chapter you can appreciate the importance and benefits that design patterns canbring to your development process. I hope you will be inspired to learn more about patterns and todevise ways you can refactor your code along solutions proposed by certain patterns.

The next chapter takes a look at what the future, albeit the very near future, has for you. LINQ and othernew features that ship with Visual Studio 2008 are discussed.

438

Part V: Refactoring Applied

79796c14.qxd:WroxPro 2/25/08 9:06 AM Page 438

Page 474: Professional Refactoring in Visual Basic

LINQ and Other VB 2008 Enhancements

Visual Studio 2008 brings probably the most important version of Visual Basic since the first versionof Visual Basic .NET. This is not because of the novelties in the integrated development environment(IDE) itself; more important are the new features that ship with version 3.5 .NET Framework andespecially interesting for those involved in refactoring, Visual Basic version 9.0.

This chapter discusses the following features:

❑ LINQ technology brings additional power to working with data in Visual Basic. Differentdata sources can now be queried in a uniform and language-native manner. LINQ pavesthe way for implementation of different technologies like object-relational mapping frame-works and others.

❑ In the 2008 version, Visual Basic is additionally equipped for working with XML. Featuressuch as XML axis properties and XML literals with embedded expressions make VisualBasic one of the best environments for working with XML.

Type Inference for Local VariablesChapter 5 discussed the VB type system in some detail. You have seen how in a statically typed lan-guage a type of variable is declared explicitly by the programmer as a part of variable declaration.You have also seen how you can defer type resolution until runtime when Option Strict is deacti-vated and variable declaration does not contain the “As” part. Finally, you have seen how you caninfer variable type based on the initial value assigned to a variable. This way, you can refactor per-missive code in order to enforce variable type declaration and explicit type conversion.

In Visual Basic 2008, Microsoft has equipped the compiler with type inference for local variables.This means that you do not have to declare a local variable explicitly; the variable will still be stati-cally typed. Take a look at Figure 15-1, for example. You can see how the IDE correctly recognizesvariable name as string.

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 439

Page 475: Professional Refactoring in Visual Basic

Figure 15-1

By omitting the “As” part of variable declaration, you can write more concise code and save yourself afew keystrokes. Nevertheless, if Option Infer is activated, the compiler will correctly infer variabletype based on the initial value, and you will be able to take advantage of all the benefits that VisualStudio IntelliSense and Visual Basic compiler can provide. Your code will be statically checked and anautocomplete and autolist members feature will be available.

Remember that you can apply type inference for local variables only. These are the variables declaredinside the method or a property. If you try to infer type for class members with Option Explicitand Option Strict deactivated, like function parameters or return values or property types, thesetypes will not be inferred. They will be late-bound instead.

While local variable type inference is a nice feature on its own, it was actually a necessary addition to the Visual Basic 2008 in order for it to support Language Integrated Query (LINQ). LINQ is probably themost prominent feature of Visual Basic 2008, and is discussed in greater detail later on in this chapter.

XML Productivity EnhancementsWhen you work with XML, you often need to construct an XML document or document fragment pro-grammatically. You can use the DOM API for this purpose, but because of its verbose nature, you willhave to write a lot of boilerplate code. This can be quite tedious.

Your other option is to implement this by writing XML literals that you use to initialize DOM objects.The problem with this approach is that you have to construct XML by concatenating strings. You don’tget any support from Visual Studio for this task, and if you need to edit the XML in some XML-awareeditor, you have to clean up all string concatenation characters and vice versa. Actually, this was the situ-ation until Visual Basic 2008 was released. In Visual Basic 2008 you can embed your XML directly insideyour Visual Basic code.

XML LiteralsWhen using XML literals, you can treat XML inside your Visual Basic code in the same form as if youwere dealing with a standalone XML document. What’s more, Visual Studio will provide typical produc-tivity enhancements such as syntax coloring and syntax checking, helping you to edit your XML effi-ciently inside your Visual Basic editor. Take a look at the embedded XML in Figure 15-2.

You probably noticed that the variable customer in Figure 15-2 is declared as an XElement. XElementbelongs to a new set of classes that Microsoft has provided for working with XML and that ship with the.NET Framework 3.5. They belong to a System.Xml.Linq namespace, and they are similar to the docu-ment object model (DOM) classes.

440

Part V: Refactoring Applied

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 440

Page 476: Professional Refactoring in Visual Basic

Figure 15-2

With embedded XML you can use a template mechanism to populate a document with data. The syntaxfor templating XML is similar to Active Server Pages syntax. You use <%= as opening tag for VB codeinside XML and %> as closing tag. Take a look at the item element in the following XML fragment:

Dim item As Item = Purchase.GetItem(0)

Dim customer As XElement = <customer name=”John Doe” id=”1323223”><history>

<purchase date=”23.03.2007”><item id=<%= item.Id %>/>

</purchase></history>

</customer>

You can see how the item element id attribute value is obtained from common VB item variable. Withall these XML literal capabilities, there is no need to construct XML through string concatenation. In caseyou have some XML constructed by string concatenation, you will be better off replacing such code withan XML literal. This leads to the first refactoring in this chapter.

Smell: XML String LiteralsDetecting the SmellUse visual inspection to detect this smell. XML strings are easily spotted because of anXML distinctive syntax.

Related RefactoringUse Replace XML String Literals with XML Literal refactoring to eliminate this smell.

RationaleIn VB 2008 XML can be embedded inside your Visual Basic code in the form of XML literals. This makes using simple string literals to represent XML obsolete,tedious, and error prone. When using strings instead of XML literals, you will not getIntelliSense support for XML or syntax checking. XML literals can be easily extractedto a separate file or copied to an external editor. This is not straightforward whenusing strings; you will first have to get rid of all double quotes and string concatena-tion symbols.

441

Chapter 15: LINQ and Other VB 2008 Enhancements

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 441

Page 477: Professional Refactoring in Visual Basic

Refactoring: Replace XML String Literals with XML LiteralMotivationXML literals let you effectively mix VB code with XML. When you use XML literals,you do not have to concatenate line breaks or enclose XML segments inside doublequotes. This means that you can paste XML inside your VB code without having tomodify it, or you can copy XML from your VB code and paste it directly inside XMLeditor, as is. Also, when using XML literals you can count on full IntelliSense support.This is a superior way of writing XML inside your code compared to simple XMLstring concatenation. In order to take advantage of full XML support that the IDE pro-vides, and in order to be able to write clean XML inside your code, replace XML stringliterals with XML literals.

Related SmellsUse this refactoring to eliminate the XML String Literals smell.

MechanicsIn order to perform this refactoring efficiently, you should understand the rough correspondence between classes in System.Xml and the new XML API classes inSystem.Xml.Linq namespace. You will see that for some classes the name in the two namespaces differs only by prefix. In System.Xml, names begin with Xml, for example, XmlDocument, and in the System.Xml.Linq names begin with X, forexample, XDocument. Take a look at Table 15-1 for a list of corresponding classes inthe two namespaces.

The refactoring is performed in a few sequential steps.

1. Start by replacing XmlDocument, XmlNode, and other DOM objects fromthe System.Xml namespace with objects from the System.Xml.Linqnamespace. Replace XmlDocument with XDocument and XmlNode withXElement. Use the shared Parse method of XDocument to loadXDocument with an XML string representation in place of the LoadXmlmethod of XmlDocument.

Dim document As XDocument = New XDocumentdocument = XDocument.Parse( _“<customer name=”“John Smith”“ id=”“335434”“/>”)

2. Now remove all string delimiters, double quote escape, and concatenationsymbols and turn the string into an XML literal. Remove the call to theXDocument.Parse shared method and initialize XDocument directly fromthe XML literal.

3. Add XML document declaration at the beginning of XML literal, for example:<?xml version=”1.0”?>.

BeforeDim document As XmlDocument = New XmlDocumentdocument.LoadXml(“<customer name=”“John Smith”“ id=”“335434”“/>”)

442

Part V: Refactoring Applied

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 442

Page 478: Professional Refactoring in Visual Basic

Table 15-1: Correspondence between System.Xml and System.Xml.Linq Namespace

Replacing XML Strings with XML LiteralsIn order to demonstrate how you can replace XML strings with XML literals I will need a suitable example.In Chapter 2 you will find one such example in the Calories Calculator application. If you take a look atListing 2-11 you will see how XML strings are used to inflate DOM objects, and in Listing 2-12 you will seethe final version of the PatientHistoryXMLStorage class.

You can apply Replace an XML strings with XML Literals refactoring on the CreateXmlDocumentFirstTimemethod. Remember, this is a bit different from its final form at the end of Chapter 2 (Listing 2-11). You cantake a look at the resulting method, once XML strings are replaced with XML literals, in Listing 15-1.

Listing 15-1: CreateXmlDocumentFirstTime Method after XML Strings Are Replacedwith XML Literals

Private Sub CreateXmlDocumentFirstTime()documentValue = _<?xml version=”1.0”?><patientsHistory>

<patient ssn=<%= patientValue.SSN %>firstName=<%= patientValue.FirstName %>lastName=<%= patientValue.LastName %>><measurement date=<%= DateTime.Today %>>

<height><%= patientValue.HeightInInches.ToString %></height><weight><%= patientValue.WeightInPounds.ToString %></weight><age><%= patientValue.Age.ToString %></age><dailyCaloriesRecommended>

Continued

System.Xml Class System.Xml.Linq Class

XmlDocument XDocument

XmlElement XElement

XmlAttribute XAttribute

XmlNode XNode

XmlText XText

XmlComment XComment

AfterDim document As XDocumentdocument = <?xml version=”1.0”?>

<customer name=”John Smith” id=”335434”/>

443

Chapter 15: LINQ and Other VB 2008 Enhancements

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 443

Page 479: Professional Refactoring in Visual Basic

Listing 15-1: CreateXmlDocumentFirstTime Method after XML Strings Are Replacedwith XML Literals (continued)

<%= patientValue.DailyCaloriesRecommended.ToString() %></dailyCaloriesRecommended><idealBodyWeight>

<%= patientValue.IdealBodyWeight.ToString() %></idealBodyWeight><distanceFromIdealWeight>

<%= patientValue.DistanceFromIdealWeight.ToString() %></distanceFromIdealWeight>

</measurement></patient>

</patientsHistory>End Sub

You can see how the XML declaration is added at the beginning of the XML literal. Without the XMLdocument declaration line, the compiler would infer the literal as an XElement, not an XDocument. Youcan also see how an ASP-like template syntax is used to embed values inside the XML. You’ll come backto the Calories Calculator example later on in this chapter, but now it’s time to take a look at anotherimportant novelty feature in Visual Basic 2008.

As an interesting side note, XML literals are not part of the C# 3.0 version published with VisualStudio 2008. This means that Visual Basic has a certain advantage over C# when you’re working withembedded XML. This advantage comes with the price of additional complexity in language syntax.

Navigating XML with XML Axis PropertiesBecause XML objects are now first-class Visual Basic citizens, you can use XML syntax to navigate theXML tree. You can query XML documents using a very compact flavor of LINQ syntax based on XMLand in a way similar to XPath. LINQ is discussed later on in this chapter, but in case you are wonderingwhat this syntax looks like, here’s an example. Take a look at Listing 15-1. Imagine now you want tonavigate the XML and obtain the Social Security number of the first patient in the patient history file.The statement will look like this:

Dim ssn = documentValue.<patientsHistory>(0).<patient>(0).@ssn

As you can see, it is similar to a property syntax in Visual Basic, where child elements in XML are refer-enced by a period. You can use the index syntax (0) to obtain a child element at a specific position, andfinally you can use the “At” sign (@) to reference a certain attribute of an element. Such code is muchmore compact than the same code written using DOM or System.Xml.Linq objects.

Extract XML Literal to Resource in Refactor!In some circumstances, it is more convenient to have an XML document declaration in a separate fileand not embedded inside your Visual Basic source. If your XML is embedded inside your Visual Basiccode, the only way to change the XML is to change the source and to recompile the project. And if youneed to pass your XML to somebody who is not a Visual Basic programmer, you will probably preferpassing a simple XML file to passing Visual Basic source code.

In case your literal contains some Visual Basic embedded expression, through <%= ... %> tag syntax,Refactor! will not display the Extract XML Literal to Resource option in the Refactor! menu, because such

444

Part V: Refactoring Applied

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 444

Page 480: Professional Refactoring in Visual Basic

a literal cannot be extracted automatically. If you have a lot of XML and only a few embedded values, it stillmight be worth extracting such literal. In that case, remove all embedded expressions from XML and usestandard VB code to assign values in continuation. Once you have cleansed your XML literal from embed-ded expressions, you can use Refactor! to extract such a literal to a resource file.

The Extract XML Literal to Resource refactoring is invoked by the pressing the right mouse button any-where over the XML literal. Take a look at Figure 15-3 to see the option selected in the IDE.

Figure 15-3

Once you have performed the refactoring, the resulting code will read XML from the resource and loadthe XDocument or XElement object with the extracted XML. The resulting code looks like this:

Dim customer As XElement = XElement.Parse(My.Resources.XMLFile)

If you take a look at the Resources.resx file, you will find the entry containing extracted XML.

Querying the Objects with LINQLINQ stands for Language Integrated Query. LINQ adds native querying capabilities to Visual Basicthrough syntax similar to SQL. With LINQ, you can enumerate, filter, and create projections on a numberof collection types, arrays, XML objects, database sources, and other objects.

Take a look at some of the benefits that LINQ brings to the VB programming language:

❑ Querying syntax is now native to Visual Basic. This effectively means that a number of new key-words are added to the language. Instead of complex Visual Basic code often based on iteratingobjects, now you can write a single query statement to locate the object that you’re searching for.

❑ No matter the object of your query, the query syntax is always the same. You can query a collection or an array, an XML document or a SQL database, and the code you write willalways be the same. This approach is inspired by the Microsoft vision of unified data accessand should simplify the way Visual Basic programmers view data.

❑ Now that the LINQ is an integral part of the Visual Basic language, you can count on thesame support of the IDE as your normal Visual Basic code has, with features such as syntaxchecking and IntelliSense working with LINQ queries as well. Remember writing embeddedSQL statements inside Visual Basic code as strings and copying them over to SQL debugger inorder to test them? No more of that. This should significantly boost your productivity.

❑ LINQ paves the way for implementing an object-relational mapping layer in .NET in a mannerthat is tightly coupled with the language itself.

Before going any further, take a look at a simple LINQ query example, so you have a better idea of howit works. Listing 15-2 shows LINQ in action.

445

Chapter 15: LINQ and Other VB 2008 Enhancements

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 445

Page 481: Professional Refactoring in Visual Basic

Listing 15-2: Basic LINQ Query Example

Option Explicit OnOption Strict OnOption Infer On

Imports System.Collections.Generic

Public Class AuthorPublic FirstName As StringPublic LastName As StringPublic Books As Book()

End Class

Public Class BookPublic Name As String

End Class

Module LinqExample

Sub Main()Dim authors As Author() = { _

New Author With { _.FirstName = “Leo”, .LastName = “Tolstoy”, _.Books = New Book() { _

New Book With {.Name = “War and Peace”}, _New Book With {.Name = “Anna Karenina”}, _New Book With {.Name = “Resurrection”} _

} _}, _

New Author With { _.FirstName = “Homer”, .LastName = “”, _.Books = New Book() { _

New Book With {.Name = “The Iliad”}, _New Book With {.Name = “Odyssey”} _

} _}, _

New Author With { _.FirstName = “Miguel”, .LastName = “Cervantes”, _.Books = New Book() { _

New Book With {.Name = “Don Quixote”} _} _

} _}

Dim authorWithMostBooks = (From author In authors _Order By author.Books.Count Descending _Select author).First

Console.WriteLine(“Author with most books: “ + _authorWithMostBooks.FirstName + _“ “ + _authorWithMostBooks.LastName)

Console.ReadKey()End Sub

End Module446

Part V: Refactoring Applied

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 446

Page 482: Professional Refactoring in Visual Basic

In this example you have the two classes: Author and Book. Each Author has an array of Book. In sub-routine Main I first created and initialized an array of authors, so I could have some data to query upon.I used the new array initializer syntax to create a ready-to-use array of Author objects. Finally, I queriedthe array by first ordering descending authors by the number of books and second selecting the firstauthor from the ordered list by the means of the operator First. This way I’m able to find the authorwith the most books among authors in the array.

Writing this query with LINQ was rather simple. Accomplishing the same in standard Visual Basic with-out using the LINQ means iterating over elements in the array and then finding the one with the biggestbook count. There are a number of implementation details that would have to be resolved. It would bedefinitely more verbose than the simple LINQ query. As queries become more complex, the power ofLINQ becomes more evident.

LINQ is a world on its own; there are a number of aggregate, groping, restriction, partitioning, and otheroperators that add to the expressiveness of LINQ language. Still, LINQ can preserve the feel of SQL sothat you will be able to write your first LINQ queries fairly quickly. Start by writing simple queries andwork your way up to more advanced LINQ features and soon you will appreciate the power of LINQdata query capabilities.

Now that you have a fairly good understanding of various features that the new version of Visual Basic brings, such as LINQ and XML literals, you can take a look at an example that brings all these features together.

Refactoring: Replace Complex VB Queries with LINQMotivationLINQ adds powerful data querying capabilities to the Visual Basic language. Theexpressiveness of LINQ means that complex Visual Basic query code can be replacedwith compact, easy-to-understand LINQ syntax.

Related SmellsUse this refactoring to replace complex query code with the more expressive LINQ syntax.

MechanicsIn case the query code is long and complex and inside a very long method, you shouldstart by extracting query code to a separate method. This way, you separate query codecompletely from the rest of the code in the method, and you can crystallize the inten-tion behind the query. It is important to project the desired result of the query, becauseexpressing it through LINQ will be probably easier than understanding the underpin-nings of Visual Basic code used for the same purpose.

BeforeDim authorWithMostBooks As Author = Nothing

For Each author In authorsIf authorWithMostBooks Is Nothing OrElse _author.Books.Count > authorWithMostBooks.Books.Count

Descending _Select author).First

447

Chapter 15: LINQ and Other VB 2008 Enhancements

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 447

Page 483: Professional Refactoring in Visual Basic

Old Example in New RobesThe Calories Calculator example from Chapter 2, written in VB 2005, has all the elements of Visual Basicsyntax that can be upgraded by the refactorings discussed in this chapter.

XML Literals in Calories CalculatorPreviously in this chapter, in Listing 15-1, you could already witness how XML strings are replaced withXML literals and embedded expressions. Because I needed to use parts of the same XML structure onmore than one occasion, I have performed method extraction on CreateXmlDocumentFirstTime andobtained two new methods: CreatePatientElement and CreateMeasurementElement. This helpedeliminate two methods: AddNewPatient and SetMeasurementValues, because new methods were per-forming the same function.

Querying Patient History with LINQIn FindPatientNode a nested loop is used to iterate over elements in a document and then over attrib-utes of an element. Take another look at the method:

Private Function FindPatientNode() As XmlNodeDim patientNode As XmlNode = NothingFor Each node As XmlNode In _ documentValue.FirstChild.ChildNodes

For Each attrib As XmlAttribute In node.Attributes‘We will use SSN to uniquely identify patientIf (attrib.Name = “ssn” And _attrib.Value = patientValue.SSN) Then

patientNode = nodeEnd If

NextNextReturn patientNode

End Function

Now, this is not that easy to understand. You have to know the underlying XML very well to understandan expression like this: documentValue.FirstChild.ChildNodes. This code is a good candidate forreplacement with a LINQ query. Code written in LINQ syntax looks like this:

Private Function FindPatientElement() As XElementDim resultingPatients = _

From patients In documentValue...<patient> _Select patients Where patients.@ssn = patientValue.SSN

Return resultingPatients(0)End Function

Not only is the method now much more compact, but it also reads almost naturally. The method alsodemonstrates the combination of LINQ and XML axis properties syntax.

In case you’re curious to see what the PatientHistoryXMLStorage class finally ended up looking like,and so you could understand better the refactorings performed, you can find the code for the entire classin Listing 15-3.

448

Part V: Refactoring Applied

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 448

Page 484: Professional Refactoring in Visual Basic

Listing 15-3: PatientHistoryXMLStorage Refactored to LINQ and XML Literals

Option Infer OnOption Strict OnOption Explicit On

Imports System.XmlImports System.IOImports System.Xml.LinqImports System.Linq

Public Class PatientHistoryXMLStoragePrivate Const HistoryXmlFileName As String = “patientHistory.xml”Private Const ExeFileName As String = “CaloriesCalculator.exe”

Private patientValue As PatientPrivate documentValue As XDocumentPublic Shared patientHistoryXmlFile As String = _CalculateXmlHistoryFilePathNextToExe()

Private Shared Function CalculateXmlHistoryFilePathNextToExe() _As String

Return System.Reflection.Assembly. _GetExecutingAssembly.Location.Replace( _ExeFileName, HistoryXmlFileName)

End Function

Private Sub LoadPatientHistoryFile()documentValue = XDocument.Load(patientHistoryXmlFile)

End Sub

Private Sub CreateXmlDocumentFirstTime()documentValue = <?xml version=”1.0”?>

<patientsHistory></patientsHistory>

documentValue.Root.Add(CreatePatientElement)Dim patient = documentValue...<patient>(0)patient.Add(CreateMeasurementElement())

End Sub

Private Function CreatePatientElement() As XElementDim patient = <patient ssn=<%= patientValue.SSN %>

firstName=<%= patientValue.FirstName %>lastName=<%= patientValue.LastName %>>

</patient>Return patient

End Function

Private Function CreateMeasurementElement() As XElementDim measurement = <measurement date=<%= DateTime.Today %>>

<height><%= patientValue.HeightInInches.ToString %>

</height>Continued

449

Chapter 15: LINQ and Other VB 2008 Enhancements

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 449

Page 485: Professional Refactoring in Visual Basic

Listing 15-3: PatientHistoryXMLStorage Refactored to LINQ and XML Literals (continued)

<weight><%= patientValue.WeightInPounds.ToString %>

</weight><age>

<%= patientValue.Age.ToString %></age><dailyCaloriesRecommended>

<%= patientValue.DailyCaloriesRecommended.ToString %></dailyCaloriesRecommended><idealBodyWeight>

<%= patientValue.IdealBodyWeight.ToString %></idealBodyWeight><distanceFromIdealWeight>

<%= patientValue.DistanceFromIdealWeight.ToString %></distanceFromIdealWeight>

</measurement>Return measurement

End Function

Private Function FindPatientElement() As XElementDim resultingPatients = _

From patients In documentValue...<patient> _Select patients Where patients.@ssn = patientValue.SSN

Return resultingPatients(0)End Function

Private Sub AddNewPatient()Dim patient = CreatePatientElement()patient.Add(CreateMeasurementElement())Me.documentValue.Root.Add(patient)

End Sub

Private Function FileCreated() As BooleanDim created As Boolean = TrueTry

LoadPatientHistoryFile()Catch noFile As IO.FileNotFoundException

created = FalseEnd TryReturn created

End Function

Public Sub Save(ByVal patient As Patient)patientValue = patientIf Not FileCreated() Then

CreateXmlDocumentFirstTime()Else

Dim patientElement As XElement = FindPatientElement()If patientElement Is Nothing Then

AddNewPatient()Else

patientElement.Add(CreateMeasurementElement())End If

450

Part V: Refactoring Applied

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 450

Page 486: Professional Refactoring in Visual Basic

Listing 15-3: PatientHistoryXMLStorage Refactored to LINQ and XML Literals (continued)

End IfdocumentValue.Save(patientHistoryXmlFile)

End SubEnd Class

Now you can take a look at how LINQ can be applied in yet another area. LINQ is revolutionizing theway VB programmers interact with databases.

Object-Relational Mapping with LINQ to SQLLINQ to SQL is Microsoft’s first attempt at object-relational mapping (ORM). ORM frameworks resolveproblematic relational persistence for your Plain Ol’ CLR Objects (POCOs) and are aimed at minimizingor eliminating effects of object-relational impedance mismatch. (Object-relational impedance mismatchis discussed in Chapter 11).

Simply put, when using an ORM framework, all you need to do is to declare, in a file or through othertypes of metadata like .NET attributes, the way your classes are mapped to tables in the database, andyou let ORM do its magic, just as depicted in Figure 15-4. But how do ORM frameworks resolve the mismatch?

❑ ORM frameworks are capable of generating SQL statements themselves, thus eliminating theneed for programming the SQL code manually.

❑ ORM frameworks are capable of generating data store schemas, relieving programmers fromthe task of designing and creating data store structures.

❑ ORM frameworks know how to interact with a data store and how to map results of queries toPOCOs and back, removing any data store–related code from the view of the programmer.

❑ Some ORM frameworks know how to generate .NET classes based on the data store schema.This feature can be useful as a staring point when creating applications that use already existingdata stores.

❑ Because mappings are generally declared (not programmed) in mapping files or applied in theform of mapping attributes, objects are isolated from structural changes in the data store. Whenthe data store structure changes, it is often enough to modify the mapping and not the code ofthe mapped class in order to keep the application working.

❑ Because ORM frameworks know how to generate SQL for each specific data store they support,changing from one data store product to another can be as easy as changing the ORM configura-tion data. This way, your code is not married to a specific data store product, thus eliminatingvendor lock-in.

Figure 15-4

Customer+Id : Integer+FirstName : String+LastName : String+Email : String

ORM FirstNameLastNameEmail

CustomerPK Id

451

Chapter 15: LINQ and Other VB 2008 Enhancements

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 451

Page 487: Professional Refactoring in Visual Basic

ORM frameworks are not without their problems. These are first and foremost related to performanceissues, as many ORM skeptics will tell you. In my experience, as is generally the case when performanceissues are concerned, these problems are overrated. In order to get the maximum out of your ORMframework, you might need to tweak the mappings, generate a data store schema, and experiment withlazy loading and cache strategies. For some expensive queries that need to retrieve and work upon hugequantities of data, you’d be better off if you write your own SQL and execute it directly against the data-base because most ORM frameworks provide a back door for direct communication with your database.

LINQ to SQL at a GlanceWith LINQ, Microsoft has greatly improved traditional object-relational mapping. LINQ integrates dataquery and makes it an integral part of VB .NET (and C#) programming language. Querying a database isnow achieved through the same syntax as when querying arrays or other “queryable” objects. The syn-tax is part of the .NET programming language, so there is no need to learn SQL or a SQL dialect specificto a certain database in order to communicate with any database supported by LINQ to SQL. (Currently,only Microsoft SQL server can be used with a LINQ-to-SQL database, but other LINQ-to-SQL providerimplementations will certainly be incoming in the near future). This also means that LINQ queries cancount on the full support of a compiler and IDE-like syntax checking, coloring, and IntelliSense.

DataContext ClassThe base class in LINQ to SQL is the DataContext found in System.Data.Linq namespace. TheDataContext instance functions use an underlying Connection object. You can instantiate DataContextby supplying a connection string or IDbConnection instance to a DataContext constructor. In additionto methods typical of any connection object such as ExecuteQuery or ExecuteCommand, DataContextboasts CreateDatabase and DeleteDatabase methods that can be used for database schema creationand disposal.

The DataContext class contains a crucial SubmitChanges method that commits all changes made onentity classes to the database, synchronizing the two worlds.

Table ClassBy calling a generic version of the GetTable method of DataContext, you obtain an instance of theSystem.Data.Linq.Table class, a central class for querying the database. This class is generic, andwhen using it, you should parameterize it with the entity class you wish to map to the database. TheTable class implements a System.Linq.IQueryable interface, meaning that you can write LINQqueries against this class.

System.Data.Linq.Mapping Namespace Attribute ClassesThe world of relational data is represented through the DataContext and Table classes. The world of .NETentities is represented by domain classes. Now you need some way to relate the two worlds. You need to tell

Definition: The class that is being mapped to a table or tables in a database isreferred to by the term entity class.

452

Part V: Refactoring Applied

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 452

Page 488: Professional Refactoring in Visual Basic

the LINQ-to-SQL framework which property of which class maps to which column of which table in thedatabase. The mapping information can be passed through to LINQ to SQL by the following means:

❑ Mapping configuration file — These files have a dbml extension and are written in XML for-mat. Here is the sample excerpt:

<Database Name=”northwnd” Class=”Northwnd”xmlns=”http://schemas.microsoft.com/dsltools/DLinqML”>

<Table Name=”Customers”><Type Name=”Customer”>

<Column Name=”CustomerID” Type=”System.String”DbType=”NChar(5) NOT NULL” IsPrimaryKey=”True” CanBeNull=”False” />

❑ Attribute based mapping — This style of mapping is achieved by applying attribute metadataover entity classes. For example:

<Table(Name:=”Customers”)> _Public Class Customer

Private lastNameValue As String‘ ...<Column(Name:=”LastName”, DBType:=”NVarChar(20) NOT NULL”, _

CanBeNull:=False), IsPrimaryKey:=False> _Public Property LastName() As String

GetReturn Me.lastNameValue

End GetSet

Me.lastNameValue = valueEnd Set

End PropertyEnd Class

Using attributes means that you will have less configuration files to worry about and might sim-plify your development. On the other hand, using attributes means that any change in mappingresults in recompilation of your application.

Continued

Refactoring: Replace Programmatic Data Layer with LINQ to SQLMotivationLINQ to SQL abstracts you from programming in database-specific language and makesyour code database neutral. You can keep your objects POCOs, without loading themwith persistence-specific code. You can switch the underlying database with minimumeffort and often can change the way entity classes are mapped to database schema with-out any intervention in VB code.

Related SmellsUse this refactoring to simplify the persistence layer.

453

Chapter 15: LINQ and Other VB 2008 Enhancements

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 453

Page 489: Professional Refactoring in Visual Basic

LINQ and the Rent-a-Wheels ApplicationI have managed to separate the data layer in the Rent-a-Wheels application and abstract the rest of theapplication from relational persistence code. However, current implementation is far from being onthe industrial level. The truth is that implementing a successful object-relational layer is not a simplefeat. While the data layer will perform satisfactorily in a single-user environment, this would not bethe case if the assembly were placed server-side.

If placed in a highly demanding server environment with lots of concurrent access, concurrency-relatedproblems would certainly start to show up. Here are some problems the current version of the data per-sistence layer fails to address:

❑ Object identity — There is no relationship between object and database identity. At any givenmoment you can have a number of different objects in application memory that have the samedatabase ID. This means that if one object has been changed in one place in memory, other objectswith the same database ID somewhere else in the memory will not be affected, ending up hold-ing obsolete data.

❑ Phantom updates and optimistic locking — I have adopted an optimistic locking schema in theapplication, but there is no verification that the underlying data was not changed (by some otheruser, for example) from the moment it was read when committing updates.

❑ Rudimentary query capabilities — Data classes provide a few query methods that are notcapable of expressing more complex query logic. As a consequence, each time a new query isnecessary, the only solution is to implement a new ad hoc SQL statement.

❑ Performance related issues: lazy loading, caching, and so on — These are some of the typicalperformance features that quality ORM frameworks provide. The data layer in the Rent-a-Wheels application does not address any of these issues.

The next part of this section shows you new data persistence layer implementation based on LINQ toSQL. However, you will not eliminate the existing layer. Because LINQ to SQL does not support anydatabase other than Microsoft SQL, you still might have a use for it.

Extracting Persistence Layer Interfaces and Restructuring Class Hierarchy

Start by extracting interfaces for BranchData, ModelData, VehicleData, and VehicleCategoryData.First, move extracted interfaces to a separate file and then to a new assembly named RentAWheels.Data.Base.

MechanicsIf you already have a well-defined, strongly typed data access layer, this refactoringshould be fairly simple to perform. Start by extracting interfaces for the data accesslayer classes and make your new LINQ-to-SQL data access classes implement theseinterfaces. Once you have the new layer ready, inject the new LINQ-to-SQL data accessclasses instead of the traditional data access classes that communicate directly with the database.

454

Part V: Refactoring Applied

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 454

Page 490: Professional Refactoring in Visual Basic

Make your new LINQ-to-SQL persistence classes implement these interfaces. Use the same names for theseclasses as for the existing ones: BranchData, ModelData, VehicleData, and VehicleCategoryData, butyou will be able to differentiate them because they belong to a different namespace. This way, it is enoughto change a single Imports statement in the Application Assembler (ApplicationEvents class) in orderto inject a different persistence layer provider. Take a look at the extracted interface shown in Listing 15-4.

Listing 15-4: Extracted IBranchData Interface Provides Type Safe Data Operations

Option Explicit OnOption Strict On

Imports RentAWheel.Business

Public Interface IBranchDataSub Delete(ByVal branch As Branch)Sub Insert(ByVal branch As Branch)Sub Update(ByVal branch As Branch)Function GetAll() As IList(Of Branch)

End Interface

This interface now implements the existing BranchData class and will be also implemented by the newBranchData based on LINQ-to-SQL technology. You can take a look at the original BranchData inListing 15-5 now that the IBranchData interface has been extracted.

Listing 15-5: BranchData after Interface Extraction

Option Strict OnOption Explicit On

Imports System.Data.SqlClientImports RentAWheel.Data.Implementation.ColumnNamesImports RentAWheel.DataImports RentAWheel.BusinessImports RentAWheel.Data.BaseImports System.Configuration

Public Class BranchDataInherits AbstractAdoData(Of Branch)Implements IBranchData

Private Const SelectAllFromBranchSql As String = _“Select * from Branch”Private Const DeleteBranchSql As String = _“Delete Branch Where BranchId = @Id”Private Const InsertBranchSql As String = _“Insert Into Branch (BranchName) Values(@Name)“Private Const UpdateBranchSql As String = _“Update Branch Set BranchName = @Name Where BranchId = @Id”

Private Const IdParamterName As String = “@Id”Private Const NameParameterName As String = “@Name”

Public Sub New(ByVal settings As ConnectionStringSettings)Continued

455

Chapter 15: LINQ and Other VB 2008 Enhancements

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 455

Page 491: Professional Refactoring in Visual Basic

Listing 15-5: BranchData after Interface Extraction (continued)

MyBase.New(settings)End Sub

Public Overrides Sub Delete(ByVal branch As Branch) _Implements IBranchData.Delete

Dim command As IDbCommand = MyBase.DbProviderFactory.CreateCommandAddParameter(command, IdParamterName, DbType.Int32, _branch.Id)ExecuteNonQuery(command, DeleteBranchSql)

End Sub

Public Overrides Sub Insert(ByVal branch As Branch) _Implements IBranchData.Insert

Dim command As IDbCommand = MyBase.DbProviderFactory.CreateCommandAddParameter(command, NameParameterName, DbType.String, _branch.Name)ExecuteNonQuery(command, InsertBranchSql)

End Sub

Public Overrides Sub Update(ByVal branch As Branch) _Implements IBranchData.Update

Dim command As IDbCommand = MyBase.DbProviderFactory.CreateCommandAddParameter(command, NameParameterName, DbType.String, _branch.Name)AddParameter(command, IdParamterName, DbType.Int32, _branch.Id)ExecuteNonQuery(command, UpdateBranchSql)

End Sub

Public Overrides Function GetAll() As IList(Of Branch) _Implements IBranchData.GetAll

Dim command As IDbCommand = MyBase.DbProviderFactory.CreateCommandDim branchesSet As DataSet = FillDataset(command, _SelectAllFromBranchSql)Dim table As DataTable = branchesSet.Tables(0)Dim branches As IList(Of Branch) = New List(Of Branch)For Each row As DataRow In table.Rows

branches.Add(New Branch(CInt(row.Item(BranchTable.Id)), _row.Item(BranchTable.Name).ToString))

NextReturn branches

End FunctionEnd Class

You can observe that this class inherits AbstractAdoData. It used to be AbstractData, but I had toreorganize the hierarchy a bit now that SQL to LINQ has to be implemented. Here is what the top of thedata classes hierarchy now looks like — take a look at Listings 15-6 through 15-8.

Listing 15-6: AbstractData

Option Strict OnOption Explicit On

456

Part V: Refactoring Applied

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 456

Page 492: Professional Refactoring in Visual Basic

Listing 15-6: AbstractData (continued)

Imports System.Configuration

Public Class AbstractDataPrivate connectionStringSettingsValue _As ConnectionStringSettings

Public Sub New(ByVal settings As ConnectionStringSettings)Me.ConnectionStringSettings = settings

End Sub

Public Overridable Property ConnectionStringSettings() _As ConnectionStringSettings

GetReturn connectionStringSettingsValue

End GetSet(ByVal value As ConnectionStringSettings)

connectionStringSettingsValue = valueEnd Set

End PropertyEnd Class

AbstractData does not contain a lot of functionality; still this will be enough to have all data classesinjected with configuration settings (connection string and data provider information) in the same way.

Listing 15-7: AbstractAdoData

Option Strict OnOption Explicit On

Imports System.DataImports System.Data.CommonImports System.Configuration

Public MustInherit Class AbstractAdoData(Of PersistedObject)Inherits AbstractData

Private dbProviderFactoryValue As DbProviderFactoryPrivate connectionStringSettingsValue As ConnectionStringSettings

Public Sub New(ByVal settings As ConnectionStringSettings)MyBase.New(settings)

End Sub

Public Overrides Property ConnectionStringSettings() _As ConnectionStringSettings

GetReturn connectionStringSettingsValue

End GetSet(ByVal value As ConnectionStringSettings)

MyBase.ConnectionStringSettings = valueContinued

457

Chapter 15: LINQ and Other VB 2008 Enhancements

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 457

Page 493: Professional Refactoring in Visual Basic

Listing 15-7: AbstractAdoData (continued)

Me.DbProviderFactory = DbProviderFactories.GetFactory( _MyBase.ConnectionStringSettings.ProviderName)

End SetEnd Property

Protected Property DbProviderFactory() As DbProviderFactoryGet

Return dbProviderFactoryValueEnd GetSet(ByVal value As DbProviderFactory)

dbProviderFactoryValue = valueEnd Set

End Property

Private Function CreateConnection() As IDbConnectionDim connection As IDbConnection = _DbProviderFactory.CreateConnection()connection.ConnectionString = _MyBase.ConnectionStringSettings.ConnectionStringReturn connection

End Function

Private Function PrepareDataObjects(ByVal command As IDbCommand, _ByVal sql As String) As IDbConnection

Dim connection As IDbConnection = CreateConnection()connection.Open()command.Connection = connectioncommand.CommandText = sqlReturn connection

End Function

Protected Sub AddParameter(ByVal command As IDbCommand, _ByVal parameterName As String, ByVal parameterType As DbType, _ByVal paramaterValue As Object)

Dim parameter As IDbDataParameter = command.CreateParameter()parameter.ParameterName = parameterNameparameter.DbType = parameterTypeparameter.Value = paramaterValuecommand.Parameters.Add(parameter)

End Sub

Protected Function FillDataset(ByVal command As IDbCommand, _ByVal sql As String) As DataSet

Dim connection As IDbConnection = PrepareDataObjects(command, sql)Dim adapter As IDbDataAdapter = DbProviderFactory.CreateDataAdapterDim dataSet As New DataSetadapter.SelectCommand = commandadapter.Fill(dataSet)connection.Close()Return dataSet

End Function

Protected Sub ExecuteNonQuery(ByVal command As IDbCommand, _

458

Part V: Refactoring Applied

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 458

Page 494: Professional Refactoring in Visual Basic

Listing 15-7: AbstractAdoData (continued)

ByVal sql As String)Dim connection As IDbConnection = PrepareDataObjects(command, sql)command.ExecuteNonQuery()connection.Close()

End Sub

Public MustOverride Function GetAll() As IList(Of PersistedObject)

Public MustOverride Sub Delete(ByVal persisted As PersistedObject)

Public MustOverride Sub Update(ByVal persisted As PersistedObject)

Public MustOverride Sub Insert(ByVal persisted As PersistedObject)

End Class

AbstractAdoData contains the code you are already familiar with from the previous chapters. The newclass is AbstractLinqData, shown in Listing 15-8.

Listing 15-8: AbstractLinqData Is the Base Class for Data Classes Based on LINQ to SQL

Option Explicit OnOption Strict On

Imports System.Data.LinqImports System.ConfigurationImports System.Data.Common

Public Class AbstractLinqDataInherits AbstractData

Private contextValue As DataContextPrivate connectionStringSettingsValue As ConnectionStringSettings

Public Sub New(ByVal settings As ConnectionStringSettings)MyBase.New(settings)

End Sub

Public Overrides Property ConnectionStringSettings() _As ConnectionStringSettings

GetReturn connectionStringSettingsValue

End GetSet(ByVal value As ConnectionStringSettings)

MyBase.ConnectionStringSettings = valueMe.Context = New DataContext(value.ConnectionString)

End SetEnd Property

Public Property Context() As DataContextGet

Continued

459

Chapter 15: LINQ and Other VB 2008 Enhancements

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 459

Page 495: Professional Refactoring in Visual Basic

Listing 15-8: AbstractLinqData Is the Base Class for Data Classes Based on LINQ to SQL (continued)

Return contextValueEnd GetSet(ByVal value As DataContext)

contextValue = valueEnd Set

End PropertyEnd Class

Now you are ready to start with the LINQ-to-SQL data classes implementation. Start by adjusting entity classes.

Applying Attributes to Entity ClassesI have decided to use attribute-based mapping for LINQ to SQL. This means that classes and propertiesattributes have to be marked by appropriate TableAttribute and ColumnAttribute classes. The goodthing about metadata (attributes) is that if you do not need them, they do not stand in the way. This meansthat the entity classes from the RentAWheel.Business namespace work with standard data classes evenwith attributes applied.

Some additional tweaking must be performed on entity classes:

❑ Adding default constructor — Entity classes mapped with LINQ to SQL have to possess adefault (parameter-less) constructor method.

❑ Using EntityRef for property in one-to-one relationship — LINQ is capable of resolving therelationship between classes, but in order to do that, the private property value has to bechanged to EntityRef(Of RelatedClass) type.

❑ Adding to classes the property to represent the Id field of related classes — For example, theModel class has a property of type VehicleCategory. VehicleCategoryId has to be added tothe Model class.

❑ Changing database type of columns mapping to enums from tinyint to integer.

❑ Changing enumerators to base 1.

The Model class represents well the changes performed on the entity classes. You can see the Model classcode in Listing 15-9.

Listing 15-9: Entity Class Example after LINQ-to-SQL Adaptation

Option Explicit OnOption Strict On

Imports System.Data.Linq.MappingImports System.Data.Linq

<Table()> _Public Class Model

Private idValue As Integer

460

Part V: Refactoring Applied

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 460

Page 496: Professional Refactoring in Visual Basic

Listing 15-9: Entity Class Example after LINQ-to-SQL Adaptation (continued)

Private nameValue As String<Column()> _Public CategoryId As IntegerPrivate categoryValue As EntityRef(Of VehicleCategory)

Public Sub New()‘needed by Linq to Sql implementation

End Sub

Public Sub New(ByVal id As Integer, _ByVal name As String, ByVal category As VehicleCategory)

Me.Id = idMe.Name = nameMe.Category = category

End Sub

<Column(IsPrimaryKey:=True, IsDbGenerated:=True, Name:=”ModelId”)> _Public Property Id() As Integer

GetReturn idValue

End GetSet(ByVal value As Integer)

idValue = valueEnd Set

End Property

<Column(Name:=”ModelName”)> _Public Property Name() As String

GetReturn nameValue

End GetSet(ByVal value As String)

nameValue = valueEnd Set

End Property

<Association(Storage:=”categoryValue”, ThisKey:=”CategoryId”)> _Public Property Category() As VehicleCategory

GetReturn categoryValue.Entity

End GetSet(ByVal value As VehicleCategory)

categoryValue.Entity = valueEnd Set

End Property

End Class

You can see in this class an example of the use of another attribute from the System.Data.Linq.Mappingnamespace. This is the Association attribute. It is used to map the property that holds the value of anotherrelated mapped entity class.

461

Chapter 15: LINQ and Other VB 2008 Enhancements

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 461

Page 497: Professional Refactoring in Visual Basic

You can now see the implementation of the LINQ-to-SQL data class. Take a look at VehicleData inListing 15-10.

Listing 15-10: Example of LINQ-to-SQL Data Class — VehicleData

Option Explicit OnOption Strict On

Imports System.Collections.GenericImports System.Data.LinqImports RentAWheel.BusinessImports RentAWheel.Data.ImplementationImports System.ConfigurationImports RentAWheel.Data.Base

Public Class VehicleDataInherits AbstractLinqDataImplements IVehicleData

Private vehicles As Table(Of Vehicle)

Public ReadOnly Property Queryable() As IQueryable(Of Vehicle)Get

Return vehiclesEnd Get

End Property

Public Sub New(ByVal settings As ConnectionStringSettings)MyBase.New(settings)vehicles = Context.GetTable(Of Vehicle)()

End Sub

Public Sub Delete(ByVal vehicle As Business.Vehicle) _Implements IVehicleData.Delete

vehicles.DeleteOnSubmit(vehicle)Context.SubmitChanges()

End Sub

Public Function GetAll() As IList(Of Business.Vehicle) _Implements IVehicleData.GetAll

Dim allVehicles = _From queried In vehicles _Select queriedReturn allVehicles.ToList

End Function

<Obsolete(“Use Queriable instead”)> _Public Function GetByCriteria(ByVal operational As Business.Operational, _

ByVal rentalState As Business.RentalState, _ByVal branchId As Integer, _ByVal categoryId As Integer) _As IList(Of Business.Vehicle) _Implements IVehicleData.GetByCriteria

If (operational = Nothing And _rentalState = Nothing _

462

Part V: Refactoring Applied

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 462

Page 498: Professional Refactoring in Visual Basic

Listing 15-10: Example of LINQ-to-SQL Data Class — VehicleData (continued)

And branchId = Nothing _And categoryId = Nothing) Then

Return Me.GetAll()End If

Dim allVehicles = _From queried In vehicles _Where (queried.Operational = operational Or operational = Nothing) And _(queried.RentalState = rentalState Or rentalState = Nothing) And _(queried.BranchId = branchId Or branchId = Nothing) And _(queried.Model.CategoryId = categoryId Or categoryId = Nothing) _Select queriedReturn allVehicles.ToList

End Function

Public Sub Insert(ByVal vehicle As Business.Vehicle) _Implements IVehicleData.Insert

vehicles.DeleteOnSubmit(vehicle)Context.SubmitChanges()

End Sub

Public Sub Update(ByVal vehicle As Business.Vehicle) _Implements IVehicleData.Update

Context.SubmitChanges()End Sub

End Class

One interesting detail is the read-only property Queryable. It permits writing LINQ queries againstVehicleData, resolving the problem of complex query logic. Take note, however, that this propertydoes not belong to the IVehicleData interface, because it cannot be supported by a standard versionof VehicleData. In order to use this property, the client has to downcast the VehicleData instancefrom IVehicleData to VehicleData.

One curious effect of XML literal support in Visual Basic is discussed next.

Using XML Literals Side Effect to Embed Formatted SQLWith XML literals you can embed text values inside XML tags, and these values preserve multiline for-matting and spacing. Placing SQL inside these tags can be a smart way to preserve SQL formatting thatdoes not contain string delimiters and concatenation signs. This way, SQL code can be pasted directly toand from a SQL and Visual Basic editor. Take a look at an example of the XML literals for the purpose ofembedding SQL in the standard BranchData class in Listing 15-11.

Listing 15-11: XML Literals Help Embed SQL Statements and Preserve Formatting

Private Shared Function SelectModelJoinCategorySql() As StringReturn <sql>

Select Model.ModelId As ModelId, Model.ModelName as ModelName, Category.CategoryName, Category.CategoryId,

Continued

463

Chapter 15: LINQ and Other VB 2008 Enhancements

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 463

Page 499: Professional Refactoring in Visual Basic

Listing 15-11: XML Literals Help Embed SQL Statements and Preserve Formatting (continued)

Category.DailyPrice, Category.WeeklyPrice, Category.MonthlyPrice from Model Inner Join Category On Model.CategoryId = Category.CategoryId

</sql>.Value.TrimEnd Function

When you download this chapter version of Rent-a-Wheels from www.wrox.com, don’t forget tochange the type of columns in the Vehicle table from tinyint to integer.

It is enough to compare two versions of the data classes to witness the advancement that LINQ brings toVisual Basic: efficient, database-agnostic persistence layer implementation with no SQL code and withquery capabilities as strong as the SQL. Once more providers become available, LINQ to SQL is poisedto become a preferred technology for implementing persistence in .NET.

SummaryThe future holds great promise for Visual Basic. With features such as LINQ and XML literals, 2008 can bethe most productive version of Visual Basic ever. Compared to previous versions, code written in VB 2008is more expressive, compact, and powerful. Many new applications of these features, such as LINQ-to-SQLsupport for different database providers, are yet to be expected.

In case you are working with the code written in some previous version of Visual Basic, such code cannottake advantage of these new Visual Basic 2008 features. In those cases you can retrofit the code with thenew features, making it more compact and expressive.

If your code is creating XML structure through simple string literals, you can replace XML strings withXML literals. If that same XML structure is being populated with data through the DOM API, you canuse embedded expressions in XML literals instead.

When writing code that searches for any type of data in Visual Basic, you will most probably write thecode that iterates some structure and has a lot of complicated implementation details. Such code, writtenin imperative style, can be much better expressed through declarative syntax. Replace your imperativestyle query code with LINQ.

LINQ brings object-relational mapping to .NET in full swing. ORM frameworks can help you create anindustrial-strength persistence layer for your objects with minimum effort. Solutions based on ORM will be more database-independent, will insulate better your Visual Basic code from database structure changes,and will help you write less code than classic solutions based on SQL. And you can replace your classicpersistence layer with a new one, based on LINQ.

The space of one chapter is too small for all the novelties that the 2008 version of VB is bringing. Staytuned with new versions of Refactor! and experiment yourself to discover new refactorings that can beperformed with this latest version of VB.

464

Part V: Refactoring Applied

79796c15.qxd:WroxPro 2/25/08 9:08 AM Page 464

Page 500: Professional Refactoring in Visual Basic

The Future of Legacy VB Code

You have seen the current state of the art of Visual Basic programming and refactoring so far inthis book. And in the previous chapter you have seen what the future holds for Visual Basic. It istime to make the circle full. In this chapter you will look back at the history of Visual Basic. Youwill see how refactoring can be relevant to dealing with the significant and still existing code baseof legacy Visual Basic code. In this chapter you will not see many new refactorings; however, youwill see how you can apply already known refactorings to upgrade legacy VB code.

Pre-.NET versions of Visual Basic made VB during the 1990s the most popular programming toolon the planet. The simplicity and power of Visual Basic, promoted under the Rapid ApplicationDevelopment (RAD) concept, won the sympathies of a multitude of developers who needed a no-frills tool for instant results.

The success of classic Visual Basic (VB 6 and previous) and its loyal user base proved to be oneof the biggest hurdles to adoption of Visual Basic .NET. Many VB 6 programmers were contentwith their tool and decided to keep using it. This decision was further fueled by two characteris-tics of VB .NET:

❑ VB .NET brings a significant rupture in syntax and other language characteristics (object-oriented versus object-based, and so on) compared to classic VB.

❑ Classic VB code is not easily migrated to VB .NET.

I will not take part in the Visual Basic .NET versus classic Visual Basic debate, because I considerit to be in the area of each programmer’s personal preferences. There is no doubt, however, thatVB .NET is now a better choice in a commercial and technological sense, and more and more pro-grammers and organizations are switching from classic VB to VB .NET or some other languagelike C# or Java.

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 465

Page 501: Professional Refactoring in Visual Basic

Unfortunately, the migration process from VB 6 to VB .NET is by no means smooth. In case you stillhaven’t started with migration, you are certainly considering it. This chapter starts by dealing with thefollowing question: should you migrate from VB 6 code to VB .NET?

This chapter deals mostly with VB 6 legacy code, because the VB 6 code base is probably the largest ofall pre-.NET Visual Basic versions. If your code is pre-VB 6, then you will have to put additional effortinto migration, more so the older your code is. In addition, you will not be able to count on the help ofthe Upgrade Wizard.

To Migrate or Not To MigrateThis decision will be mostly influenced by the value of your existing Visual Basic 6 code base. If youplan to use and evolve the code further, then you should consider migrating. On the other hand, if youneed the functionality the code provides, but the code itself is difficult to maintain and has reached theend of useful life, instead of migration, you should consider a complete rewrite in .NET. Most com-monly, you will migrate certain parts of your solution, phasing out VB 6 code gradually. You shouldchoose the path that suits your specific situation. Whatever line of action you choose, there are someaspects of the migration process that you should be aware of before embarking on it.

Migration versus UpgradeWith the term migration, I refer to the basic process of transforming Visual Basic 6 codeto Visual Basic .NET syntax so it can run in the .NET run time correctly. The process ofmigration is focused on obtaining the functional equivalent of VB 6 code in .NET. Thisprocess can be partially automated through Microsoft’s Visual Basic Upgrade Wizard.

A VB 6 upgrade to .NET is geared toward obtaining code that is benefiting from newlanguage capabilities, idioms, and patterns characteristic of Visual Basic .NET code.This process has to be preceded by Visual Basic 6 to .NET migration and is based ondifferent refactorings that are applied to raw migrated code.

Are Classic VB and VB .NET the Same Programming Language?VB .NET is an evolution of VB 6 programming language, and this is reflected by theversioning policy that Microsoft applies to the Visual Basic language. The first versionof VB .NET is also known as VB 7 while the version that ships with Visual Studio 2008is known as VB 9.

However, changes introduced in the first version of .NET when compared to VB 6 meanthat there is no backward compatibility between the two. If you compare this to the rela-tionship between the C and C++ programming languages, you will see that almost any Ccode can be compiled with a C++ compiler, making C and C++ in that aspect more closelyrelated than VB and VB .NET. New language capabilities like full object orientation andmetadata in the forms of attributes, generics, and so on, require a significant paradigmshift from the programmer’s point of view. That is why when embarking on Visual Basic.NET and coming from classic Visual Basic, you are better off viewing VB .NET as a com-pletely new programming language that deserves thorough study, practice, and signifi-cant assimilation time.

466

Part V: Refactoring Applied

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 466

Page 502: Professional Refactoring in Visual Basic

Much information is available on the subject of migration, ready tool support, and so on. However, onceyou have performed the migration, and your legacy code can execute in .NET, you have only one halfdone. In order to benefit from .NET, you need to upgrade your code through a refactoring process so itbecomes written in VB .NET spirit. This chapter deals with the upgrade process in much greater detail.

Migration Cannot Be 100 Percent AutomatedMicrosoft ships the Visual Basic Upgrade Wizard as a part of Visual Studio. The tool can be invoked byselecting a File ➪ Open ➪ Convert option in the Visual Studio menu. This tool is capable of taking yourVisual Basic 6 code as an input and creating a new Visual Basic .NET project containing migrated code.Unfortunately, the tool will rarely migrate 100 percent of your code. Real-life production code will bemigrated with varying success rate, depending on numerous factors:

❑ The use of syntax, functions, and keywords like GoSub/Return, and so on.

❑ The use of conditional compilation

❑ The use of controls, methods, properties, and events

If you use any of these that have no direct equivalent in .NET, the Upgrade Wizard cannot migrate thecode, and you will end up with numerous ‘UPGRADE_ISSUE: Feature X is not supported com-ments inside your code and blocks of code that do not compile in .NET. You will have to resolve suchissues manually.

VB 6 and VB .NET Code Can Interoperate.NET comes with a built-in COM interoperability solution. The interoperability goes both ways. COMcomponents can consume services provided by .NET components, and .NET components can invokeCOM components.

You can reference Visual Basic 6 and other COM libraries from your .NET project, instantiate COMobjects, and invoke their methods. This is accomplished through a runtime callable wrapper (RCW)that makes a COM component look like a .NET component. This option is very useful for a gradualmigration approach. This way, you can start your migration by upgrading your presentation layer to.NET first, then your domain, and finally the persistence layer.

The COM callable wrapper (CCW) lets you expose your .NET classes to the COM world. You will beable to reference .NET libraries from your VB 6 integrated development environment (IDE). VisualStudio .NET lets you choose the COM component template when you add items to your project. Onceyou build your .NET project, you will see a new item in the list of available references in VB 6 IDE. TheCOM name of the class will be in the form of [Project Name].[Class Name]. This opens another setof possibilities for gradual migration. You can start with the migration of the persistence layer and thenmove gradually toward the presentation layer. This will be possible on the condition that your applica-tion is not monolithic and is partitioned along three-tiered architecture. Take a look at how CCW andRCW are enabling .NET–COM interoperability in Figure 16-1.

The decision on how to deal with your legacy code is a complex one. However, the market often dictatescertain conclusions. .NET has been widely accepted, and it’s perceived as a cutting-edge technology forbusiness applications development. In that sense, VB 6 is becoming less and less competitive.

467

Chapter 16: The Future of Legacy VB Code

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 467

Page 503: Professional Refactoring in Visual Basic

Figure 16-1

The best choice for you depends on specific circumstances in your case. You might decide to stick withVB 6 code, upgrade it, or simply discard this code and perform a complete rewrite. A comparison of dif-ferent options is shown in Table 16-1.

Table 16-1: Legacy Code Upgrade Options

I assume that the majority of you have probably already chosen the gradual upgrade path to .NET. Inthat case there are some tools that you might find useful in the process. The next section of the chapterdiscusses these tools.

Decision Factors No Upgrade Upgrade Rewrite

Initial cost None Moderate High

Effort None Medium High

Risk None Medium Medium

Competitiveness Low Medium High

Different technologies used simultaneously

No Yes No

Skill upgrade cost None High High

Automation Not relevant Medium None

Third-party support (components,tools, training, consulting, and so on)

Low (Declining) Medium High

Positive image and marketing Low High High

COM Client

.NET Client

CCW

.NETClass

RCW

COMClass

468

Part V: Refactoring Applied

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 468

Page 504: Professional Refactoring in Visual Basic

Migration Tools and LibrariesMicrosoft provides a set of free tools and libraries that can help you with the migration process and easethe burden to a large degree. There are also third-party commercial tools that go beyond basic functionalityprovided by Upgrade Wizard.

Code Advisor for Visual Basic 6This tool installs as a Visual Basic 6 add-in. Its purpose is to analyze your existing Visual Basic 6 codeand to mark migration-problematic code. It will help you identify code you should modify before evenattempting migration. It is a lot easier to modify a fully functional Visual Basic 6 code than to try to fixbroken Visual Basic .NET code that resulted from a poorly executed migration process. Using code advi-sory before attempting automated migration with the help of Visual Basic Upgrade Wizard will result in a much higher upgrade success rate. You can find the download link for Code Advisor at this URL:http://msdn2.microsoft.com/en-us/vbasic/ms789135.aspx.

Visual Basic 6 Upgrade Assessment ToolThe purpose of this tool is to help you estimate the cost and effort needed for migration of a certain project. The Assessment tool gives you a detailed breakdown of manual adjustments that have to be performed, estimating necessary effort in terms of hours and even giving you an estimated amount indollars. You can configure different calculation parameters such as the cost of programmer per hour sothey fit your specific situation. You can find the download link for the Upgrade Assessment Tool at thisURL: http://msdn2.microsoft.com/en-us/library/aa480541.aspx.

The Microsoft.VisualBasic.Compatibility AssemblyThis assembly contains classes that help you migrate VB 6 code that is using types that have no directequivalent in the .NET Framework. You should strive toward phasing out the use of this assembly,because it is provided for compatibility purposes only.

Visual Basic Upgrade WizardPart of the Visual Studio distribution, this wizard will perform the migration work by creating a new .NETproject with corresponding classes based on an input VB 6 project. Migration success largely depends ontweaking your code beforehand, with the help of Code Advisor, or running the wizard for the first time foranalysis purposes only.

ArtinSoft Visual Basic Upgrade CompanionArtinSoft Upgrade Companion goes beyond the Upgrade Wizard. It can perform type inference and error-handling upgrades and supports migration of a number of additional ActiveX controls to .NET equivalents.You can learn more about Upgrade Companion at www.artinsoft.com/pr_vbcompanion.aspx.

Most of the modifications to your Visual Basic 6 code that precede the migration process will be basedon the advice obtained from VB 6 Code Advisor. In addition, you should perform some refactorings inorder to simplify the migration process. The next section of this chapter mentions a few.

469

Chapter 16: The Future of Legacy VB Code

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 469

Page 505: Professional Refactoring in Visual Basic

Preliminary VB 6 RefactoringsEven before embarking on migration, you can perform some refactorings on Visual Basic 6 code that willease the burden of the migration process to follow. You can start out by taking care of basic hygiene: youcan eliminate dead code and unused references.

Breaking the MonolithThroughout the book, you have seen the value of the divide and conquer principle. If you are dealing with a large code base, it is best if you can approach migration in a gradual manner, with the help of.NET–COM interop or by simply migrating and testing different pieces and then putting everythingtogether. Attempting migration on large projects in one go will result in a huge broken code base, andyou will not know where to start in order to make things work. This is especially true for applicationsthat are not divided into components and consist of a single executable or library. This is the you shouldperform Break Monolith refactoring beforehand when migrating such projects.

Smell: Monolithic ApplicationDetecting the SmellA good indication of a monolithic application is a single binary file. However, just the physical view of an application will not tell you a lot about internal application design.

If classes are well designed, but only placed into a single binary, then separating classesin the form of dynamically linked libraries is often a simple affair. If classes are poorlydesigned, for example all classes in your project are forms, then breaking the physicalstructure into smaller pieces (see Break Monolith refactoring) has to be preceded byclass-level (logical model) redesign of your application.

Client-server applications that use a database as a simple persistence and transactionalmechanism and internally embed SQL with business logic do not differ significantly intheir substance from classical monolithic applications. In those cases, breaking such anapplication into smaller physical parts is applicable just as well.

Related RefactoringUse Break the Monolith refactoring to eliminate this smell.

RationaleNonmodular, single-tiered applications are much more difficult to maintain, test, and dis-tribute. They do not promote reuse. Modular design is the most important tool for com-bating complexity in software design. This means that monolithic applications are oftenmuch more complex, and in order to make these applications maintainable, breakingthem into smaller parts such as libraries and components is often the only solution fortheir longevity.

470

Part V: Refactoring Applied

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 470

Page 506: Professional Refactoring in Visual Basic

Figure 16-2

Figure 16-3

Refactoring: Break MonolithMotivationMonolithic applications are single-tiered, self-contained applications, nonmodular con-structs. Such applications are difficult to maintain, because the effects of changes arenot isolated and they do not promote encapsulation on a large scale. They do not pro-mote reuse, except reuse that can be performed internally, adding to tight coupling andinterdependency of the code.

From a migration point of view, such applications are much more difficult to transform to.NET because you have to perform the migration process in an “all or nothing” manner.This means that once you perform the automated step of migration process, you will endup with huge quantities of nonfunctional, broken code. Nor will you be able to test smallparts of an application in isolation. Therefore, you should break your monolithic applica-tion into smaller parts that are easier to migrate and test in a gradual manner.

Related SmellsUse this refactoring to eliminate the Monolithic Application smell and to break theapplication into smaller parts like components or services.

MechanicsThis is large scale refactoring that is comprised of many smaller refactorings. In orderto break the monolith, you have to extract assemblies, move and rename classes andnamespaces, and organize your project around typical architectural tiers. Take a look at Chapter 13 for more details on the related refactorings.

BeforeBefore Break Monolith refactoring is performed, you are generally dealing with a singleexecutable, as shown in Figure 16-2.

AfterAfter the extraction, your application comprises more components, often assembledalong architectural tiers, as shown in Figure 16-3.

«executable»Monolith

«executable»Presentation Layer

«library»Persistence Layer

«library»Domain Layer

471

Chapter 16: The Future of Legacy VB Code

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 471

Page 507: Professional Refactoring in Visual Basic

Performing Break Monolith refactoring before migration is a valid option if you can count on a battery of functional tests that will help you perform this restructuring on your VB 6 code effectively and with-out introducing any new bugs. Unfortunately, you do not often have functional, or even less common,unit tests for your legacy code at your disposal. In these cases, it makes more sense to migrate the codeand implement the tests only after the migration. This leads to the section of the chapter coming shortlythat talks about techniques you can use to put your freshly migrated code under a testing harness.

Dealing with Conditional CompilationThe Upgrade Wizard does not react gracefully when confronted with conditional compilation blocks.You will most probably end up with VB 6 code pasted “as is” inside the block in your migrated class. In order to deal with this issue, you can employ a very simple methodology.

Comment out all your conditional compilation statements before invoking the Upgrade Wizard. TheUpgrade Wizard will not erase the comments. Once you have finished working with the wizard, erasethe comment sign and turn your conditional compilation statements back on. This way, you will success-fully migrate code that would be otherwise ignored by Upgrade Wizard.

Putting Your Migrated Code under a Testing Harness

Back in Chapter 3 of this book, you can find a short introduction to unit testing and the NUnit frame-work. The NUnit framework was written in order to facilitate automated unit testing. Unit testing meanstesting the smallest testable part of your application, generally a method of a property, in isolation. Witha test-driven approach, meaning test first, implementing unit tests is feasible. Implementing unit tests aposteriori, on already finished code, is quite a different matter altogether. Putting your freshly migratedcode under unit tests is generally highly complex and has to be done gradually, on par with furtherrefactorings of your migrated code.

This leaves you in a quandary. In order to refactor migrated code, you need a testing backup, an assur-ance that your refactoring is not introducing bugs to the migrated code base. And in order to put yourcode under unit tests, you need to refactor your code for dependency injection. This looks like thevicious circle. So, what is the solution?

Introducing a Functional Testing HarnessInstead of starting off with unit tests, you can create a functional testing harness that will help you startwith refactoring. Functional testing means performing tests from the user’s point of view. Whenever auser interacts with the system, he or she does so in order to achieve a certain goal. If you can simulatethe user action and then verify that the goal is achieved, you have the basic insurance that the system isperforming as it should. As you progress toward better designed, better encapsulated code, extract andconsolidate classes, you can add new unit tests to already existing functional test base.

Definition: Functional testing verifies that the system behavior is correct from theuser’s point of view and according to requirements.

472

Part V: Refactoring Applied

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 472

Page 508: Professional Refactoring in Visual Basic

The basic difference between unit and functional testing is that functional tests result in execution of thecomplete execution flow. They do not test single method or property. This makes them less reliable anduseful as refactoring guardian than unit tests from two aspects:

❑ It is difficult to achieve high code coverage with functional tests. Because you are testingthe complete execution flow, the code will inevitably branch in different directions some-where along the line. You can try changing initial parameters and test for limit values and boundary conditions; nevertheless, you will most probably never achieve 100 percent code coverage.

❑ In case of error, it is more difficult to identify and pin down the exact source of error. Theerror has been produced somewhere along in the execution stack. You can catch the error, butfinding the exact offending line requires a lot of work such as debugging and the like.

While there are some downsides to functional testing harnesses, it is probably the best we can hope forwhen starting to work on legacy code. Let’s see how you can implement a functional testing harnessover your legacy code.

Implementing a Functional Testing HarnessWhile not originally created for this purpose, NUnit can serve as a good functional testing framework.You can use NUnit to invoke your tests automatically and to verify the results of test execution.

You construct tests using NUnit by implementing test classes with test methods that exercise the classunder scrutiny. You mark such methods with the Test attribute. When writing functional tests, youwrite the following code in the body of test methods:

❑ Code that simulates user action on the system

❑ Code that verifies the expected result of user action on the system

I have prepared a very basic example to illustrate an implementation of functional tests using NUnit.Take a look at Figure 16-4. It shows a very simple form that can be used to create users in some imagi-nary system.

Figure 16-4

When dealing with real-life applications, you will probably deal with much more complex code, but theunderlying approach will be the same. You can take a look at the form’s code in Listing 16-1.

473

Chapter 16: The Future of Legacy VB Code

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 473

Page 509: Professional Refactoring in Visual Basic

Listing 16-1: Sample Legacy Form — UserMaintenance

Option Explicit OnOption Strict On

Imports System.Data.SqlClient

Public Class UserMaintenance‘Inherits System.Windows.Forms.Form (in partial)

Private Const connectionString As String = _“Data Source=localhost;Initial Catalog=CH16;Trusted_Connection=yes”

Private Sub CreateUser_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles CreateUser.Click

Dim insertUserSql As String = _“Insert Into Users (FirstName, LastName, Email) “ + _“Values(@FirstName, @LastName, @Email)“Dim connection As SqlConnection = _New SqlConnection(connectionString)Dim command As SqlCommand = New SqlCommand(insertUserSql)command.Connection = connectioncommand.Parameters.AddWithValue(“@FirstName”, Me.FirstName.Text)command.Parameters.AddWithValue(“@LastName”, Me.LastName.Text)command.Parameters.AddWithValue(“@Email”, Me.Email.Text)connection.Open()command.ExecuteNonQuery()connection.Close()

End SubEnd Class

In order to stimulate user action, in this case the Create User button click, you need to invoke theCreateUser_Click routine. Because this routine is private, you can extract code in the body of this routine into a new public routine called CreateNewUser. This way you have separated the code for event handling from the code that communicates with the database. In a more realistic situation, your event-handling routine would probably contain some other code, such as data validation or something similar,besides database interaction code. You can take a look at the resulting UserMaintenance form code in theListing 16-2.

Listing 16-2: UserMaintenance Form after Routine Extraction

Imports System.Data.SqlClient

Public Class UserMaintenance‘Inherits System.Windows.Forms.Form (in partial)

Public Const ConnectionString As String = _“Data Source=localhost;Initial Catalog=CH16;Trusted_Connection=yes”

Private Sub CreateUser_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles CreateUser.Click

474

Part V: Refactoring Applied

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 474

Page 510: Professional Refactoring in Visual Basic

Listing 16-2: UserMaintenance Form after Routine Extraction (continued)

CreateNewUser()End Sub

Public Sub CreateNewUser()Dim insertUserSql As String = _

“Insert Into Users (FirstName, LastName, Email) “ + _“Values(@FirstName, @LastName, @Email)“

Dim connection As SqlConnection = _New SqlConnection(connectionString)Dim command As SqlCommand = New SqlCommand(insertUserSql)command.Connection = connectioncommand.Parameters.AddWithValue(“@FirstName”, Me.FirstName.Text)command.Parameters.AddWithValue(“@LastName”, Me.LastName.Text)command.Parameters.AddWithValue(“@Email”, Me.Email.Text)connection.Open()command.ExecuteNonQuery()connection.Close()

End SubEnd Class

Now you are ready to write a functional NUnit test class. You can call the class UserMaintenanceTest.You can use the SetUp method to instantiate UserMaintenance form and open database connection inUserMaintenanceTest. Inside the CreateUser test method you perform the following:

1. Fill controls in the UserMaintenance form with some random generated data for user first andlast name and e-mail.

2. Invoke the CreateNewUser method on the UserMaintenance form.

3. Check in the database that the user was correctly inserted.

Take a look at the test fixture code in Listing 16-3.

Listing 16-3: UserMaintenance Test Class

Imports NUnit.FrameworkImports System.Data.SqlClient

<TestFixture()> _Public Class UserMaintenanceTest

Private userForm As UserMaintenancePrivate connection As IDbConnection

<SetUp()> _Public Sub Prepare()

userForm = New UserMaintenanceconnection = New SqlConnectionconnection.ConnectionString = UserMaintenance.ConnectionStringconnection.Open()

Continued

475

Chapter 16: The Future of Legacy VB Code

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 475

Page 511: Professional Refactoring in Visual Basic

Listing 16-3: UserMaintenance Test Class (continued)

End Sub

Private Shared Function RandomString() As StringRandomize()Return CStr(Hex(CLng((10000000000 * Rnd()) + 1)))

End Function

<Test()> _Public Sub CreateUser()

FillFormWithNewUserData()userForm.CreateNewUser()Dim reader As SqlDataReader = FindCreatedUserInDB()Assert.IsTrue(reader.HasRows)

End Sub

Private Function FindCreatedUserInDB() _As SqlDataReader

Dim command As SqlCommand = New SqlCommandDim findInsertedSql As String = _

“Select * from Users Where “ + _“FirstName = @FirstName and “ + _“LastName = @LastName and “ + _“Email = @Email”

command.Connection = CType(Me.connection, SqlConnection)command.CommandText = findInsertedSqlcommand.Parameters.AddWithValue(“@FirstName”, userForm.FirstName.Text)command.Parameters.AddWithValue(“@LastName”, userForm.LastName.Text)command.Parameters.AddWithValue(“@Email”, userForm.Email.Text)Dim reader As SqlDataReader = command.ExecuteReaderreader.Read()Return reader

End Function

Private Sub FillFormWithNewUserData()userForm.FirstName.Text = RandomString()userForm.LastName.Text = RandomString()userForm.Email.Text = RandomString() + “@“ + RandomString() + “.com”

End Sub

<TearDown()> _Public Sub EndTests()

connection.Close()End Sub

End Class

Now that you have tests in place, you can move further. As you progress with refactoring and youextract new classes, you can add new unit tests for them. Functional tests you can keep. They will stillserve you to test your system from a functional point of view, and as you layer your application, theyfulfill the role of integration tests.

476

Part V: Refactoring Applied

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 476

Page 512: Professional Refactoring in Visual Basic

Upgrading Your Legacy CodeYou have performed the migration; your code executes correctly on .NET. You have created the testing har-ness, and you will be able to recognize if your code stops working. Your code is now ready for the upgradeprocess. Through the numerous refactorings that make up the upgrade process, you transform your legacycode to natural .NET code that is making use of typical VB .NET language capabilities and idioms.

Strict Static TypingYour migrated code is about to go through a number of transformations. The changes that the upgradeprocess will provoke on your code are by no means minor. This is why you need to enlist all the helpyou can get.

If your code is written in strict static type style, the compiler can warn you during compile time of manyerrors even before you commit them. VB 6 code is sometimes statically typed, if the Option Explicitstatement was placed at the beginning of the file. Option Strict is new to VB .NET, so it will never beactivated in freshly migrated code.

Start your upgrade process by executing Set Option Explicit On and Set Option Strict On refactorings onyour code base. This way, you will be able to identify a number of type related errors while performingthe refactorings to follow.

Check out Chapter 5 to see how these refactorings are performed.

Moving Design from Procedural toward an Object-Oriented Paradigm

A good sign of excessive procedural code in a migrated project is a large number of functions insidemodules and the use of user-defined types. As you can see in Chapter 12, this means that data andbehavior are split one from another, and you need to bring those two together in the form of a singletype. Take a look at Listing 16-4, which shows code inside a VB 6 module. Here you can see a simplifiedexample of procedural design in VB 6 code.

Listing 16-4: Procedural VB 6 Code

Option Explicit

Public Type CustomerFirstName As StringLastName As StringIsPremier As Boolean

End Type

Public Function CalculateDiscount(customer As Customer) As DoubleIf customer.IsPremier Then

CalculateDiscount = 3Else

CalculateDiscount = 0End If

End Function

477

Chapter 16: The Future of Legacy VB Code

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 477

Page 513: Professional Refactoring in Visual Basic

Once migrated, a VB 6 type construct is replaced with a Visual Basic .NET structure. A structure in VB .NETcan have method and property members, but cannot be inherited. You can start by moving free functions to a structure. Once you place a function inside a structure, it becomes a method and no longer needsparameters to operate. Instead, it can access structure data members directly. Take a look at the exampleafter moving from procedural to object-oriented design in Listing 16-5.

Listing 16-5: Procedural to Object-Oriented Code: Type to Structure and Functioninside Structure

Option Strict OnOption Explicit On

Module MyModule

Public Structure CustomerDim FirstName As StringDim LastName As StringDim IsPremier As BooleanPublic Function CalculateDiscount() As Double

If Me.IsPremier ThenCalculateDiscount = 3

ElseCalculateDiscount = 0

End IfEnd Function

End StructureEnd Module

Because the CalculateDiscount method can access the IsPremier property directly, it no longerneeds the customer parameter. Instead of calling the CalculateDiscount directly, clients call theCalculateDiscount method on the Customer type.

The most important difference between structure and class constructs in Visual Basic .NET is that youcan inherit class while with structure you cannot. If necessary later on, you can move the structure to a separate file and replace the declaration with the keyword Class, transforming structure into class.Now you can inherit your object-oriented type.

For more background on converting from procedural to object-oriented design, consult Chapter 12.

Introducing InheritanceOne of the most hailed features introduced in VB .NET was implementation inheritance. In VB 6, youhad to resort to interface inheritance and simple delegation in order to accomplish code reuse that in VB .NET can be expressed through simple implementation inheritance.

Through inheritance you can eliminate a lot of the duplication in your code or you can spare yourselffrom writing a lot of unnecessary delegation code. In Chapter 12, you have seen how to amend such asituation through Extract Superclass refactoring.

A good place to start looking for duplication caused by lack of implementation inheritance in VB 6 is inclass-interface hierarchies. If you have a few classes that implement the same interface, it is highly probablethat you will find duplicated code in members inherited from the interface.

478

Part V: Refactoring Applied

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 478

Page 514: Professional Refactoring in Visual Basic

Take a look at Figure 16-5. The code for members inherited from the IDataAccess interface —OpenConnection, CloseConnection, StartTransaction, CommitTransaction, andRollbackTransaction in classes UserDataAccess and AccountDataAccess — is identical.

Figure 16-5

The solution is to extract the common code for both classes into the common parent class and make thisclass implement IDataInterface. This way, you insert a new abstract class in the middle of the existinghierarchy. Take a look at Figure 16-6 to see the result.

Making Use of Parameterized ConstructorIn VB 6 you had the Class_Initialize() routine at your disposal for any object initialization tasks.However, this routine could not be parameterized. In VB .NET, you can write parameterized constructors.You can use parameterized constructors to impose certain rules on object instantiation. For example, youcan oblige the client to provide some objects that you might need in order to instantiate your object. And,as a more specific application of parameterized constructor, you can use parameterized constructors toimplement a constructor-based dependency injection, as discussed in Chapter 14.

Using Generic Containers for Additional Type SafetyIn VB 6, you had a small number of containers at your disposal. There was a VB 6 language built-inCollection, a list-like container for index-based access. Then later on came the Scripting.Dictionaryobject distributed with the Windows Scripting host, a dictionary-like container for key-based access.

«interface»IDataAccess

+OpenConnection()+CloseConnection()+StartTransaction()+CommitTransaction()+RollbackTransaction()

UserDataAccess

+Insert()+Update()+Delete()+Find()+OpenConnection()+CloseConnection()+StartTransaction()+CommitTransaction()+RollbackTransaction()

AccountDataAccess

+Insert()+Update()+Delete()+Find()+OpenConnection()+CloseConnection()+StartTransaction()+CommitTransaction()+RollbackTransaction()

479

Chapter 16: The Future of Legacy VB Code

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 479

Page 515: Professional Refactoring in Visual Basic

Figure 16-6

While the choice of containers was quite limited, it was sufficient for most situations. In VB 6, you coulduse containers in one of the following manners:

❑ Bare-bones Collection and Dictionary

❑ Custom type-safe wrapper over Collection and Dictionary

The second option provided additional security for your code, meaning that only objects of the sametype could be added into the container. However, it also meant writing a lot of repetitive code. Take alook at an example of such a wrapper class in Listing 16-6.

In VB .NET you have a much greater number of container classes at your disposal. Ones available since the first edition of the .NET framework are found inside the System.Collections namespace,and newer type-safe generic collections that provide additional type safety can be found in theSystem.Collections.Generic namespace.

Listing 16-6: Example of Type-Safe Container Wrapper in VB 6

Option Explicit

Private coll As New collection

Public Sub Add(Item As Customer, Optional key As String, _Optional before As Integer, Optional after As Integer)

coll.Add Item, key, before, after

«interface»IDataAccess

+OpenConnection()+CloseConnection()+StartTransaction()+CommitTransaction()+RollbackTransaction()

DataAccess

+OpenConnection()+CloseConnection()+StartTransaction()+CommitTransaction()+RollbackTransaction()

UserDataAccess

+Insert()+Update()+Delete()+Find()

AccountDataAccess

+Insert()+Update()+Delete()+Find()

480

Part V: Refactoring Applied

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 480

Page 516: Professional Refactoring in Visual Basic

Listing 16-6: Example of Type-Safe Container Wrapper in VB 6 (continued)

End Sub

Public Function Count() As IntegerCount = coll.Count

End Function

Public Sub Remove(index As Integer)coll.Remove (index)

End Sub

Public Function Item(index As Integer) as ObjectItem = coll.Item(index)

End Sub

When upgrading VB 6 code, you should strive to replace unsafe containers with their generic counter-parts, provided the two following conditions are met:

❑ Only objects of same type are placed inside the container.

❑ If the custom type-safe wrapper is used, the wrapper is not performing any additional process-ing besides basic container function.

Take a look at the upgrade path for containers in Table 16-2.

Table 16-2: Upgrade Path for Container Types

Upgrading Exception HandlingI have devoted a whole chapter (Chapter 6) to exception handling in this book. It deals exactly with thesubject relevant to this section: upgrade of legacy to structured error handling.

Bear in mind one fact: it will be much easier for you to organize your exceptions in the form of well-designed hierarchies if you have structured your classes well. Therefore, leave this refactoring for last.Once you have extracted classes and namespaces and formed inheritance hierarchies, it will be a wholelot easier to transform VB 6 error code to full-blown exception classes.

Implementing XML CommentsXML comments in .NET are not your typical VB 6 comments. The purpose of these comments is todocument the assembly to be used in Object Browser and by IntelliSense by the client programmerthat should not be exposed to source code of your assembly. XML comments are not meant to giveadditional information when browsing the code.

VB 6 Container VB .NET Migrated Container VB .NET Upgraded Container

Collection Microsoft.VisualBasic.Collection System.Collections.Generic.List

Scripting.Dictionary Scripting.Dictionary System.Collections.Generic.Dictionary

481

Chapter 16: The Future of Legacy VB Code

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 481

Page 517: Professional Refactoring in Visual Basic

If you already have comments written with a similar purpose in mind in your VB 6 code, you can transformthose to XML format. If not, write new ones. These comments will greatly facilitate the work for programmersusing your assemblies.

Releasing Resources in .NETIn VB 6, it was recommended that you destroy resources explicitly. You would perform this by settingthe variable to Nothing. In COM, there is a reference-counting garbage collector; this has the effect ofdecrementing an instance counter and in this way helps the garbage collector clean the instance.

In .NET, the garbage collector is fundamentally different. (.NET garbage collection is discussed inChapter 11.) For the .NET tracing garbage collector, setting the variable to Nothing will not have anyeffect on resource collection dynamics. Make use of a Using–End Using block instead to guarantee thatthe runtime disposes of a resource when your code exits the block.

SummaryThe migration process of VB 6 code to VB .NET is far from straightforward. Even after basic migrationhas been performed, you will find resulting code lacking many of the good design features that can beapplied in VB .NET. After a simple migration, such code will have a definite legacy feel about it.

No need to despair. If your code is really worth it, you can upgrade it completely to VB .NET. Refactoringtechniques can be of great help in the process. Start by preparing your code still in VB 6 for the migration.Make use of tools such as the Code Advisor and Upgrade Wizard to get your code to VB .NET at the highest possible operational rate.

Once your code can be executed in .NET, be sure to create tests that will help you validate the refactoringsyou are about to perform. Putting legacy code under a testing harness is by no means simple, so start offby testing your code from a functional point of view. As you progress in redesigning your code, add moreunit tests to your test battery.

VB 6 has no implementation inheritance, generics, constructors, or strict typing. Start by making yourcode explicit and strict. This way, the compiler can give you further assurance that you are not breakinganything when modifying your code.

Look for duplicate code between sibling classes in interface implementation hierarchies, and eliminate anyduplication by extracting a superclass. Use parameterized constructors to add additional safety to yourcode. Use generic containers to reduce code base and to provide type safety to your container classes.

In this chapter, you have seen some typical situations and you have received some hints on where tostart when dealing with legacy code. After all, once your code executes in .NET, it should leave its legacyorigins behind. Treat your migrated code as any other VB .NET code and make sure you are using all theobject-oriented language features at your disposal to make such code a worthy citizen of the .NET world.

482

Part V: Refactoring Applied

79796c16.qxd:WroxPro 2/25/08 9:09 AM Page 482

Page 518: Professional Refactoring in Visual Basic

Unleash Refactor!

Thanks to its partnership with Microsoft, Developer Express has released Refactor! for VB, a freeand scaled, down version of their Refactor! Pro product. The free version has several limitations:

❑ It supports only the Visual Basic programming language.

❑ It works only with Visual Studio 2005 and 2008, while the Pro version works with VisualStudio 2002 and 2003 as well.

The Pro version lets you extend the capability of the tool and define your own refactorings; it hascode-analysis capabilities, more refactoring options, and so on.

There is one more limitation. Configuration options are visible only in the Pro version of the toolby default. However, with a little bit of tweaking, you can make a DevExpress menu item appearand obtain access to the Refactor! Options window even with the VB version!

Configuration options let you customize certain behaviors of Refactor! so they better suit your per-sonal programming style. For example, you can change the keyboard shortcut keys used for one-keyrefactoring, access different visual options, like color or contrast, and more importantly, customizetool behavior when performing different refactorings. Feel free to explore other options and set themto your liking.

So, to activate this hidden feature, you need to change a certain setting in the Windows Registry.

Depending on the selection you made during the installation process, this setting is located in oneof two places listed in Table A-1.

79796bapp01.qxd:WroxPro 2/22/08 5:01 PM Page 483

Page 519: Professional Refactoring in Visual Basic

Table A-1: Registry Setting Location for Configuration Options Window andDevExpress Menu Activation

1. Use regedit to locate this entry.

2. Then find the HideMenu key. This key has the DWORD type, and the value should be set to 1when you first find the key. Change this value to 0.

3. Once you apply the new setting, close Visual Studio and open it again so that the changes cantake effect.

4. Then open any VB source file for editing in order to load the Refactor! plug-in inside VisualStudio. The DevExpress menu item should appear now in the Visual Studio menu.

Note that the exact location of the setting might be slightly different, depending on the exact version ofRefactor! you have installed.

You can open the Options window and experiment with different settings that let you further customizethe tool. You can see the DevExpress top menu item inside the Visual Studio menu and the DevExpressOptions window in Figure A-1.

Changes in the configuration you apply in the Options window are persisted in different .ini files.Refactor! places these files inside your User Profile folder. As another option to tweak Refactor!, thesefiles can be edited manually. You can find these files in the following folder:

C:\Documents and Settings\YourProfile\Application Data\CodeRush for VS.NET\2.5\Settings\

where C: is the drive on your computer where the Windows operating system is installed, and whereYourProfile corresponds to the username under which you are logged in.

This way you will be able to use many options that are available only in the Pro version of the tool and customize Refactor! in accordance to your personal preferences.

Refactor! Installation Option Windows Registry Location

Refactor! is available to any user. HKEY_LOCAL_MACHINE\SOFTWARE\DeveloperExpress\CodeRush for VS\2.5

Refactor! is available only to thecurrent user.

HKEY_CURRENT_USER \SOFTWARE\DeveloperExpress\CodeRush for VS\2.5

484

Appendix A: Unleash Refactor!

79796bapp01.qxd:WroxPro 2/22/08 5:01 PM Page 484

Page 520: Professional Refactoring in Visual Basic

Figure A-1

485

Appendix A: Unleash Refactor!

79796bapp01.qxd:WroxPro 2/22/08 5:01 PM Page 485

Page 521: Professional Refactoring in Visual Basic

79796bapp01.qxd:WroxPro 2/22/08 5:01 PM Page 486

Page 522: Professional Refactoring in Visual Basic

Rent-a-Wheels PrototypeInternals and Intricacies

This appendix extends the scenario from Chapter 4 by investigating the implementation of theRent-a-Wheels application prototype in more detail. It continues recounting the conversationbetween Tim, the novice VB programmer who worked up the first version of the prototype, andme, focusing on two main areas of the prototype application:

❑ Relevant event-handling code

❑ Relevant form class code

Remember, this code is not meant to illustrate good design principles, but quite the opposite. It is hereto illustrate a non–object-oriented, legacy VB approach to Rapid Application Development (RAD).Try to think about better ways to implement this application. Compare your ideas with the solutionsthat I offer throughout the book while refactoring and improving the Rent-a-Wheels prototype.

Hand Over Button Click Event-Handling Code

When I asked Tim about the Hand Over button click event, he said, “In this case, there is no inter-mediate form, since no additional data is necessary for this operation. Essentially, a user can justmark a vehicle as handed over to customer. This amounts to changing the value of the columnAvailable for that vehicle to the number 2 in the database. Essentially, the code is quite similar totwo routines we examined when looking into the Rent operation code. The only difference is thatsince no other form was necessary, the whole operation is performed at once, and all code is con-tained in the BtnHandOver_Click routine in the FrmFleetView class.”

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 487

Page 523: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

488

To remind yourself what the code for the application’s Rent operation looked like, you can turn back toChapter 4, or you can download the code for the whole sample application at www.wrox.com.

We turned our attention to the Receive button next.

Receive Button Click Event-Handling CodeWhen discussing the Receive button, Tim said, “Not too much is a surprise here. This code is quite simi-lar to that hiding under the Rent button, too. The only difference is that the FrmRcv is displayed and notthe FrmRt.”

Tim showed me FrmRt, the Receive a Vehicle form, which is shown in Figure B-1.

Figure B-1

“This form is used to enter Mileage and Tank level data after vehicle reception. The code for the Receivebutton click event is very similar to code for the Rent button click event: this update sets Available to 3,and Mileage and Tank level data are saved also.”

I then said to Tim that it seemed he had used the same approach throughout the application.

Tim said, “The similarity in code is not a pure coincidence. I was able to finish this application so quicklybecause I used a lot of copy-paste with the code.”

Charge Button Click Event-Handling CodeTim continued his thought by turning to the Charge button: “As a matter of fact, there is no need to goover Charge button click event-handling code in detail. Again, similar to button Hand Over, the updateis executed right away without displaying an intermediate form.”

I said, “I guess that the SQL query code is a bit different, isn’t it?”

Tim said, “Yes, the only important difference, compared to BtnHandOver_Click routine, is in the SQLcode.” He showed me the following code:

strSql = “Update Vehicle “ + _“Set Available = 0, “ + _“Mileage = 0,” + _“Tank = 0, “ + _

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 488

Page 524: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

489

“CustomerFirstName = ‘’,” + _“CustomerLastName = ‘’,” + _“CustomerDocNumber = ‘’,” + _“CustomerDocType = ‘’ “ + _“WHERE LicensePlate = @SelectedLP”

“Column Available is set to 0, so that the vehicle can again be considered available when the receptionistsearches for vehicle a to rent. The rest of the data related to a previous rental is simply erased. In essence,a single update over the Vehicle table is executed.”

I said, “By the way, StringBuilder is more efficient at string concatenation than simple String.”

Next, we turned our attention to the Change Branch button.

Change Branch Button Click Event-Handling Code

Tim said, “The code here is similar to the rest of the buttons where an intermediate form is displayedfirst. The difference is that we need one more piece of data transferred from the grid in the main form to the intermediate form — the current branch. After that, the FrmChangeBranch is shown.”

The Change Branch click event code is shown in Listing B-1.

Listing B-1: BtnChangeBranch_Click from Receive Form Event-Handling Routine

Private Sub BtnChangeBranch_Click( _ByVal sender As System.Object, ByVal e As System.EventArgs) _Handles BtnChangeBranch.Click

‘Check that user has made a selectionIf DGridFleetView.SelectedRows.Count > 0 Then

‘Read value from first cell as Vehicle Id and set it‘as a value of TxtLP in FrmRentFrmChangeBranch.TxtLP.Text = _ DGridFleetView.SelectedRows.Item(0).Cells.Item(0).Value ‘Read value from fifth cell as Vehicle Id and set it‘as a value of TxtCurrentBranch Text prop in FrmRentFrmChangeBranch.TxtCurrentBranch.Text = _ DGridFleetView.SelectedRows.Item(0).Cells.Item(5).Value ‘Show change branch formFrmChangeBranch.Show()

Else‘Warn user if no selection made in table and exitMsgBox(“Please select vehicle first!”)

End IfEnd Sub

After we examined the event-handling code, Tim said, “The real code is in FrmChangeBranch. First,take a look at the form’s appearance.” He then showed me the Change Branch form, which is shown inFigure B-2.

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 489

Page 525: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

490

Figure B-2

“The form opens with the Vehicle License Plate and Current Branch values set. A new branch can beselected from the combo box in the form. So, users can change the branch or close the form by pressingthe Cancel button.

“In this case, I think it’s best if I take you through the complete code for this class. We can start off by examining the class declaration and then look at the methods in detail.” Tim then showed me theFrmChangeBranch class code shown in Listing B-2.

Listing B-2: Class FrmChangeBranch

‘ Importing data provider namespacesImports System.Data Imports System.Data.SqlClient

Public Class FrmChangeBranch

‘Maintain BranchId - BranchName relation in HashtablePrivate branchIdTable

‘Close form when button cancel pressedPrivate Sub BtnCancel_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles BtnCancel.Click

‘close form Me.Close()

End Sub

‘Form Load event handling routinePrivate Sub FrmChangeBranch_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load

‘method body (see next code listing)End Sub

‘Button Change Branch Click event, event handling routinePrivate Sub BtnChangeBranch_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles BtnChangeBranch.Click

‘method body … End Sub

End Class End Class

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 490

Page 526: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

491

Tim noted, “Just like the rest of the classes in the project, this class imports the data provider namespaces,as observed in import section of the class. There are three event-handling routines. BtnCancel_Clickcloses the form. The Form Load event handler is used to retrieve the data and fill the Branch combo but-ton from the Branch table. We can examine this code, and the code for the BtnChangeBranch_Clickroutine, after we’re done looking at the class code.”

I asked what the purpose of the branchIdTable hashtable field in the form was.

Tim said, “This is necessary in order to relate the name of the branch, visible to the user in the Branchcombo box text, with the branch ID, necessary for the database update statement, as can be observed inthe BtnChangeBranch_Click code. Take a look at the FrmChangeBranch_Load code, where thecombo box and the branchIdTable are filled.”

In VB 6, the combo box control had a data item property you could use for this purpose, but it was madeobsolete with VB .NET. ComboBox in the .NET Framework has a ValueMember property that fulfillsa similar purpose.

The Load event in the Change Branch code is shown in Listing B-3.

Listing B-3: FrmChangeBranch_Load Event-Handling Routine

Private Sub FrmChangeBranch_Load( _ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles MyBase.Load

‘Declare variablesDim oCnDim oCmdDim oReaderOn Error GoTo ErrorHandlerbranchIdTable = New Hashtable‘Create SqlConnectionoCn = New SqlConnection(“Data Source=TESLA-DAN;” + _“Initial Catalog=RENTAWHEELS;User ID=sa”)oCmd = New SqlCommand‘Create Sql String with parameter @SelectedLPstrSql = “Select * from Branch”‘open connectionoCn.Open()‘Set connection to commandoCmd.Connection = oCn‘set Sql string to command objectoCmd.CommandText = strSql‘SqlDataReader retrieves the data to fill branch combo ‘and branch id-name hashtable‘Execute command:oReader = oCmd.ExecuteReader() While oReader.Read

CboBranch.Items.Add(oReader(1)) ‘Add Id object to table with name as key branchIdTable.Add(oReader(1), oReader(0))

End While CboBranch.SelectedIndex = 0

Continued

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 491

Page 527: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

492

Listing B-3: FrmChangeBranch_Load Event-Handling Routine (continued)

‘close readeroReader.Close()‘close connectionoCn.Close()‘destroy objectsoCmd = NothingoCn = NothingoReader = NothingExit Sub

ErrorHandler:MsgBox(“A problem occurred “ + _“and the application can not recover! “ + _“Please contact the technical support.”)Err.Clear()

End Sub

Tim said, “I’m sure this FrmChangeBranch_Load routine code looks very familiar. It connects to data-base and executes the query that retrieves the data from the Branch table, and then this data is used tofill combo box Branch and the table that maintains the branch name-ID relationship. We’ll need the IDwhen executing an update in the BtnChangeBranch_Click routine, which we can take a look at now.”

I said, “In .NET you have another, more object-oriented form of exception handling, called structuredexception handling, at your disposal. It consists in writing Try-Catch-Finally blocks around the codeyou want to be under managed error handling.”

Listing B-4 shows the Change Branch button click event-handling code.

Listing B-4: BtnChangeBranch_Click from FrmChangeBranch Form Event-Handling Routine

Private Sub BtnChangeBranch_Click( _ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnChangeBranch.Click

‘Declare variablesDim oCnDim oCmd‘double-check with userIf MsgBox(“Are you sure?”, MsgBoxStyle.OKCancel) Then

On Error GoTo ErrorHandler‘Create SqlConnectionoCn = New SqlConnection(“Data Source=TESLA-DAN;” + _“Initial Catalog=RENTAWHEELS;User ID=sa”)oCmd = New SqlCommand‘Create Sql String with parameter @SelectedLPstrSql = “Update Vehicle “ + _

“Set BranchId = @BranchId “ + _“WHERE LicensePlate = @SelectedLP”

‘open connectionoCn.Open()‘Set connection to command

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 492

Page 528: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

493

Listing B-4: BtnChangeBranch_Click from FrmChangeBranch Form Event-Handling Routine (continued)

oCmd.Connection = oCn‘set Sql string to command objectoCmd.CommandText = strSql‘Branch name is used to obtain branch id for database update‘Add parameter to commandoCmd.Parameters.AddWithValue( _ “@BranchId”, branchIdTable.Item(CboBranch.Text)) oCmd.Parameters.AddWithValue( _“@SelectedLP”, TxtLP.Text)‘execute commandoCmd.ExecuteNonQuery()‘close connectionoCn.Close()‘destroy objectsoCmd = NothingoCn = NothingExit Sub

ErrorHandler:MsgBox(“A problem occurred “ + _“and the application can not recover! “ + _“Please contact the technical support.”)Err.Clear()

End IfEnd Sub

Tim said, “As you can see, BtnChangeBranch_Click contains the usual database-related code. A connec-tion is created and opened, an SQL statement is constructed, a command and parameters are created, andthe command is executed. There is some cleanup code and usual error-handling code. The only curiosityhere is a private field, a hashtable used to maintain relation between the branch ID and branch name. Thisway a branch ID is retrieved by using the branch name that a user selected in the Branch combo box, andthe database table update can be executed.”

To Maintenance and From MaintenanceButton Click Event Code

The next logical place for us to turn was to the two buttons left on the right-hand side, To Maintenanceand From Maintenance. Tim said, “When they are pressed, the update is executed over the databasedirectly, very much as in the Hand Over and Charge click events we already discussed. The only piece of code worth examining is the SQL command construction code.” He then showed me the code forBtnToMaintenance_Click.

‘Create Sql String with parameter @SelectedLPstrSql = “Update Vehicle “ + _

“Set Operational = 1 “ + _“WHERE LicensePlate = @SelectedLP”

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 493

Page 529: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

494

“Basically, for the selected vehicle, the Operational column is set to value 1. The query forBtnFromMaintenance_Click does exactly the opposite — it sets the value of column Operational to 0.”

‘Create Sql String with parameter @SelectedLPstrSql = “Update Vehicle “ + _

“Set Operational = 0 “ + _“WHERE LicensePlate = @SelectedLP”

With the examination of code related to buttons on the right-hand side of the Fleet View form complete,we decided to next take a look at the Administration menu Tim added to the application.

Administer Fleet FormTim said, “The only purpose behind this menu is to display different forms used for data administration.Let’s start off by taking a look at the Vehicle Fleet Administration form.” The Vehicle Fleet Administrationform is shown in Figure B-3.

Figure B-3

“As you can probably guess, the button strip at the bottom is used to navigate through the set of vehi-cles. The button with < displays the previous record, the button > the next record, and the buttons <<and >> the first and the last vehicle entry. Pressing buttons on the right-hand side triggers databasequery execution, except when the button New is pressed. The New button’s only role is to erase contentof controls on the form. However, pressing Delete provokes elimination of the current record in thedatabase. The Save button first eliminates and then saves the current record, thus performing a kind ofupdate or insert, depending on if this particular license plate number already exists in the database ornot. If the license plate number already exists in the database, eliminating the vehicle first and theninserting the vehicle equals an update. If vehicle is new, it means it does not exist in the database, sodeleting it in a first step has no effect on the database and does not raise any exception, so this equals asimple insert. That way, I was able to simplify the user interface.”

I said, “So, basically, you hold all the records from the Vehicle table in memory, and you navigatethrough the records with navigation buttons on the button strip. When you press the Delete or Save button, you issue a statement that synchronizes the local data in memory with the database.”

Tim said, “Yes, that about sums it up. We can take a look at the code for this form in detail, starting withBtnDelete_Click, since it is really straightforward.”

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 494

Page 530: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

495

Delete Button Click Event-Handling RoutineI indicated to Tim that I thought this must be another simple SQL delete query.

Tim said, “Yes, not much surprise here. This code looks very similar to a large portion of code we haveseen so far. A simple delete SQL query is executed against the Vehicle table in the database, using thevehicle license plate as a parameter.”

The SQL code for the delete query is shown in Listing B-5.

Listing B-5: SQL Query Inside BtnDelete_Click Event-Handling Routine

strSqlDelete = “Delete From Vehicle “ + _“Where LicensePlate = @LicensePlate”

New Button Click Event-Handling RoutineAfter completing our look at the Delete button code, we moved on to the New button. Tim said, “Thisbutton is rather obvious also. Components are cleared of previous values.” The code for the New buttonclick event is shown in Listing B-6.

Listing B-6: BtnNew_Click from FrmFlt Form Event-Handling Routine

Private Sub BtnNew_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnNew.Click

TxtLP.Text = “”CboBranch.Text = “”CboModel.Text = “”

End Sub

Reload Button Click Event-Handling RoutineTim indicated that another very simple routine was the one for the Reload button event handling. “Allit does is call the FrmFleet_Load routine.” The Reload Button event-handling routine is illustrated inListing B-7.

Listing B-7: BtnReload_Click from FrmFlt Form Event-Handling Routine

Private Sub BtnReload_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnReload.Click

FrmFleet_Load(Nothing, Nothing)End Sub

Then Tim said, “This all leads us to the Form Load routine. It is significantly longer when compared toroutines we have inspected so far. Let’s take a look at it.”

Form Load Event-Handling RoutineTim went on, “The code in this method is again a database-related code. The only real difference here isthat there are three queries executed instead of one. The first two queries are concerned with filling two

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 495

Page 531: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

496

combo boxes, Branch and Model, with data from Branch and Model tables. Two fields of type Hashtableare used to maintain relation between the text that goes into the combo boxes and is presented to the user(Branch and Model names) and the Branch and Model IDs needed for update and delete.

“The last query is used to retrieve data from the Vehicle table joined with other, lookup tables. This data isdisplayed to users as they navigate through the Vehicle table records. The resulting data table is assignedto a private field dtVehicles.”

You can see a rather large Fleet Form Load event in Listing B-8.

Listing B-8: FrmFleet_Load Event-Handling Routine

Private Sub FrmFleet_Load(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles MyBase.Load

‘Declare variablesDim oCnOn Error GoTo ErrorHandler‘Create SqlConnectionoCn = New SqlConnection(“Data Source=TESLA-DAN;” + _“Initial Catalog=RENTAWHEELS;User ID=sa”)‘open connectionoCn.Open()

‘LOAD BRANCH COMBO->Dim oCmdBranchDim oBranchReaderbranchIdTable = New HashtableoCmdBranch = New SqlCommand‘Create Sql String with parameter @SelectedLPstrSqlBranch = “Select * from Branch”‘Set connection to commandoCmdBranch.Connection = oCn‘set Sql string to command objectoCmdBranch.CommandText = strSqlBranch‘execute commandoBranchReader = oCmdBranch.ExecuteReader()‘Loop over records from Branch tableWhile oBranchReader.Read

‘Add Branch Name to Branch comboCboBranch.Items.Add(oBranchReader(1)) ‘Add Id object to hashtable with name as keybranchIdTable.Add(oBranchReader(1), oBranchReader(0))

End While‘close readeroBranchReader.Close()‘destroy objectsoCmdBranch = NothingoBranchReader = Nothing‘END LOAD BRANCH COMBO->

‘LOAD MODEL COMBO->Dim oCmdModelDim oModelReader

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 496

Page 532: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

497

Listing B-8: FrmFleet_Load Event-Handling Routine (continued)

modelIdTable = New HashtableoCmdModel = New SqlCommand‘Create Sql String with parameter @SelectedLPstrSqlModel = “Select * from Model”‘Set connection to commandoCmdModel.Connection = oCn‘set Sql string to command objectoCmdModel.CommandText = strSqlModel‘execute commandoModelReader = oCmdModel.ExecuteReader()‘Loop over records from Model tableWhile oModelReader.Read

CboModel.Items.Add(oModelReader(1)) ‘Add Id object to table with name as keymodelIdTable.Add(oModelReader(1), oModelReader(0))

End While‘close readeroModelReader.Close()‘destroy objectsoCmdBranch = NothingoModelReader = Nothing‘END LOAD MODEL COMBO->

‘LOAD VEHICLES TABLE->Dim oAdapter‘create data setdsVehicles = New DataSet‘create adapteroAdapter = New SqlDataAdapter‘Create SqlConnectionoCn = New SqlConnection(“Data Source=TESLA-DAN;” + _“Initial Catalog=RENTAWHEELS;User ID=sa”)oCmd = New SqlCommand‘Build SQL query that joins vehicle with rest of tablesstrSql = “Select Vehicle.LicensePlate AS LicensePlate, “ + _ “Branch.Name as BranchName, “ + _ “Model.Name as ModelName “ + _ “from Vehicle “ + _ “Inner Join Branch On “ + _ “Vehicle.BranchId = Branch.BranchId “ + _ “Inner Join Model On “ + _ “Vehicle.ModelId = Model.ModelId” ‘open connectionoCn.Open()‘Set connection to commandoCmd.Connection = oCn‘set Sql string to command objectoCmd.CommandText = strSql‘execute commandoAdapter.SelectCommand = oCmd‘fill DataSetoAdapter.Fill(dsVehicles)

Continued

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 497

Page 533: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

498

Listing B-8: FrmFleet_Load Event-Handling Routine (continued)

‘destroy objectsoCmd = NothingoAdapter = Nothing‘Assign Vehicles Sql Table object to fielddtVehicles = dsVehicles.Tables.Item(0) If (dtVehicles.Rows.Count > 0) Then

drRow = dtVehicles.Rows(0)TxtLP.Text = drRow.Item(“LicensePlate”)CboBranch.Text = drRow.Item(“BranchName”)CboModel.Text = drRow.Item(“ModelName”)currentRowIndex = 0

End If‘close connectionoCn.Close()oCn = NothingExit Sub

ErrorHandler:MsgBox(“A problem occurred “ + _“and the application can not recover! “ + _“Please contact the technical support.”)Err.Clear()

End Sub

Administer Fleet Form Class Code: FieldsTim moved on to discuss the fields and class declaration next. “Again, this class imports SQL Data Providernamespaces. There are two hashtables, branchIdTable and modelIdTable, related to two combo boxes,Branch and Model. As you have already seen in the Change Branch form, the purpose of these two tables is to maintain relation between the name displayed in the combo box and the ID of the branch or model.

“A new element here is a DataTable named dtVehicles. It represents the Vehicle table from theRENTAWHEELS database. As the user navigates between vehicles on the form, a single row from the table is displayed. The integer currentRowIndex serves to conserve an index of the currently displayed row.”

Then Tim showed me the Fleet Form class code in Listing B-9.

Listing B-9: Fleet Form Class Fields

‘Importing data provider namespacesImports System.Data Imports System.Data.SqlClient

Public Class FrmFlt

‘Maintain BranchId - BranchName relation in Hashtable Private branchIdTable ‘Maintain ModelId - ModelName relation in Hashtable Private modelIdTable

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 498

Page 534: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

499

Listing B-9: Fleet Form Class Fields (continued)

‘table VehiclesPrivate dtVehicles ‘Index of displayed rowPrivate currentRowIndex

‘rest of class code – methodsEnd Class

When we had finished looking at this code, Tim then said, “A look at the Left button event-handlingcode will show how this currentRowIndex number is used when users navigate through the Vehicletable records.”

Left Button Click Event-Handling RoutineTim said, “When someone presses BtnLeft (the one with the < symbol), the current row index getsdecremented and then used to obtain a row from the Vehicle table. This row is then used to fill controlson the form with data. In this way, the effect of moving through the records in the table using the navi-gation button on the bottom of the form is achieved.” The Left button click event-handling code isshown in Listing B-10.

Listing B-10: BtnLeft_Click Event-Handling Routine

Private Sub BtnLeft_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnLeft.Click

‘Check actual row not first already ‘and table vehicle contains dataIf (currentRowIndex >= 1 And _ dtVehicles.Rows.Count > 0) Then

‘Decrement currentRowIndexcurrentRowIndex -= 1 ‘Obtain actual row from table using currentRowIndexdrRow = dtVehicles.Rows(currentRowIndex) ‘Fill controls with data from current rowTxtLP.Text = drRow.Item(“LicensePlate”) CboBranch.Text = drRow.Item(“BranchName”) CboModel.Text = drRow.Item(“ModelName”)

End IfEnd Sub

Tim then showed that similar code is present in the rest of the navigation buttons. “The Right buttonincrements the current row index, the First button sets the current row index to zero, and the Last button sets the current row index to row count minus one.”

Private Sub BtnFirst_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnFirst.Click

If (dtVehicles.Rows.Count > 0) Then‘Current row index set to zerocurrentRowIndex = 0 drRow = dtVehicles.Rows(currentRowIndex)TxtLP.Text = drRow.Item(“LicensePlate”)

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 499

Page 535: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

500

CboBranch.Text = drRow.Item(“BranchName”)CboModel.Text = drRow.Item(“ModelName”)

End IfEnd Sub

Private Sub BtnRight_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnRight.Click

If (dtVehicles.Rows.Count > currentRowIndex + 1) Then‘Current row index incrementedcurrentRowIndex += 1 drRow = dtVehicles.Rows(currentRowIndex)TxtLP.Text = drRow.Item(“LicensePlate”)CboBranch.Text = drRow.Item(“BranchName”)CboModel.Text = drRow.Item(“ModelName”)

End IfEnd Sub

Private Sub BtnLast_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnLast.Click

If (dtVehicles.Rows.Count > 0) Then‘Current row index set to row count -1currentRowIndex = dtVehicles.Rows.Count – 1 drRow = dtVehicles.Rows(currentRowIndex)TxtLP.Text = drRow.Item(“LicensePlate”)CboBranch.Text = drRow.Item(“BranchName”)CboModel.Text = drRow.Item(“ModelName”)

End IfEnd Sub

Then Tim said, “The only button left to inspect is the Save button. The code executed when this button isactivated is probably the most complicated in the application so far.”

Save Button Click Event-Handling RoutineTim continued, “There is a single button to perform both insert and update. However, I am not able todistinguish between the update when a vehicle already exists in database and the insert when you aredealing with a completely new vehicle. To resolve this, two steps are always performed:

1. A record is deleted based on a primary key, license plate number.

2. A record with the same primary key (license plate number) is inserted.”

Tim then showed me the Save button click event-handling routine in Listing B-11.

Listing B-11: BtnSave_Click Event-Handling Routine

Private Sub BtnSave_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnSave.Click

‘Declare variablesDim oCnDim oCmdDeleteDim oCmdInsertDim oTrxOn Error GoTo ErrorHandler

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 500

Page 536: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

501

Listing B-11: BtnSave_Click Event-Handling Routine (continued)

‘Create SqlConnectionoCn = New SqlConnection(“Data Source=TESLA-DAN;” + _“Initial Catalog=RENTAWHEELS;User ID=sa”)‘Create two commands, one for delete and one for insertoCmdDelete = New SqlCommand oCmdInsert = New SqlCommand ‘Create Sql String for deletestrSqlDelete = “Delete From Vehicle “ + _ “Where LicensePlate = @LicensePlate” ‘Create Sql String for insertstrSqlInsert = “Insert Into Vehicle “ + _ “(LicensePlate, ModelId,BranchId) “ + _ “Values(@LicensePlate, @ModelId, @BranchId)“ ‘add parameter for delete oCmdDelete.Parameters.AddWithValue( _ “@LicensePlate”, TxtLP.Text) ‘add parameters for insertoCmdInsert.Parameters.AddWithValue( _ “@LicensePlate”, TxtLP.Text) oCmdInsert.Parameters.AddWithValue( _ “@ModelId”, modelIdTable(CboModel.Text)) oCmdInsert.Parameters.AddWithValue( _ “@BranchId”, branchIdTable(CboBranch.Text)) ‘open connectionoCn.Open()‘Set connection to commandoCmdDelete.Connection = oCnoCmdInsert.Connection = oCn‘set Sql string to command objectoCmdDelete.CommandText = strSqlDeleteoCmdInsert.CommandText = strSqlInsert‘start transactionoTrx = oCn.BeginTransaction() ‘enlist commands with transactionoCmdDelete.Transaction = oTrx oCmdInsert.Transaction = oTrx ‘execute command: first delete and then insert recordoCmdDelete.ExecuteNonQuery() oCmdInsert.ExecuteNonQuery() oTrx.Commit() ‘close connectionoCn.Close()‘destroy objectsoCmdDelete = NothingoCmdInsert = NothingoCn = NothingFrmFleet_Load(Nothing, Nothing)Exit Sub

ErrorHandler:MsgBox(“A problem occurred “ + _“and the application can not recover! “ + _“Please contact the technical support.”)Err.Clear()

End Sub

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 501

Page 537: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

502

“This in the end has same effect as performing SQL update. In the case of an insert, erasing non-existingrecords will not provoke any problems, since I am not checking for the number of records that wereaffected with a delete command. And just in case some problem occurs between the executions of thesetwo separate queries, they are executed under the same transaction, thus making sure that the partialexecution will not happen. That way, we can be at ease that the record will not be deleted unless theinsert is executed also.

“In the Vehicle table, the license plate number serves as the primary key. In other tables, the primary keyis autogenerated by the database itself upon each insert. This makes it possible to use SQL insert andupdate in the rest of the administration forms, because the ID is placed in a read-only text box. If the IDis present, this means the record already exists, and we need to execute update. If the ID is not present,the user has already pressed the New button that cleared the form and is trying to insert a new record,meaning that an insert command should be issued to the database. The other administration forms likeModel, Category, and Branch are based on the same model.”

Display Button Click Event-HandlingRoutine

Having finished our discussion about the administration forms, we returned to the last piece of func-tionality we needed to discuss on the application’s main form, the Display button and filtering capabili-ties it made available to users.

Tim said, “At the bottom of the main form, the user can set different search options and in that way filter the vehicles that are going to be displayed. The table is refreshed by pressing the Display button.Different combo boxes in the vehicle display filter group box are converted as part of a Where clause in anSQL Select query that is used to retrieve data from the RENTAWHEELS database. Additional complexitycomes from the fact that certain filters can be applied or not, depending on the user’s decision. For exam-ple, if the Category combo box is left with the value All, this means that Category should not form part ofthe Where clause in an SQL statement. Since there are four combo boxes, and each can form part of a Whereclause or not, there is a need to dynamically construct this query. Take a look at the code now.” With that,Tim showed me the event-handling code for the Display button that you can see in Listing B-12.

Listing B-12: BtnDisplay_Click Event-Handling Routine

Private Sub BtnDisplay_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles BtnDisplay.Click

‘Declare variablesDim oCnDim oCmdDim oAdapterDim oRdDim strSql As StringOn Error GoTo ErrorHandler‘clear grid of previous resultMe.DGridFleetView.Rows.Clear() ‘create adapteroAdapter = New SqlDataAdapter‘Create SqlConnection

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 502

Page 538: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

503

Listing B-12: BtnDisplay_Click Event-Handling Routine (continued)

oCn = New SqlConnection(“Data Source=TESLA-DAN;” + _“Initial Catalog=RENTAWHEELS;User ID=sa”)oCmd = New SqlCommand‘Construct SELECT part of SQL Query strSql = “Select Vehicle.LicensePlate as LicensePlate,” + _ “Category.Name as CategoryName,” + _ “Vehicle.Available as Available,” + _ “Vehicle.Operational as Operational,” + _ “Model.Name as ModelName,” + _ “Branch.Name as BranchName,” + _ “Category.DailyPrice as DailyPrice,” + _ “Category.WeeklyPrice as WeeklyPrice,” + _ “Category.MonthlyPrice as MonthlyPrice,” + _ “Vehicle.Mileage as Mileage,” + _ “Vehicle.Tank as Tank,” + _ “Vehicle.CustomerFirstName as FirstName,” + _ “Vehicle.CustomerLastName as LastName,” + _ “Vehicle.CustomerDocNumber as DocNumber,” + _ “Vehicle.CustomerDocType as DocType “ + _ “from Vehicle “ + _ ‘Construct INNER JOIN part of SQL Query “Inner Join Model ON “ + _ “Vehicle.ModelId = Model.ModelId “ + _ “Inner Join Branch ON “ + _ “Vehicle.BranchId = Branch.BranchId “ + _ “Inner Join Category ON “ + _ “Model.CategoryId = Category.CategoryId” ‘Check that at least one filter applied If (CboAvailable.Text <> “All” Or _

CboBranch.Text <> “All” Or _ CboCategory.Text <> “All” Or _ CboOperational.Text <> “All”) Then strSql += “ Where “ ‘Concatenate “Available” conditionIf (CboAvailable.Text <> “All”) Then

strSql += “Vehicle.Available = @Available And “ ‘Convert “Available” textual value to integerSelect Case CboAvailable.Text

Case “Available” available = 0

Case “Hand Over” available = 1

Case “Rented” available = 2

Case “Charge” available = 3

End Select ‘Add “Available” command parameteroCmd.Parameters.AddWithValue( _ “@Available”, available)

End If‘Concatenate “Branch” condition

Continued

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 503

Page 539: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

504

Listing B-12: BtnDisplay_Click Event-Handling Routine (continued)

If CboBranch.Text <> “All” Then strSql += “Vehicle.BranchId = @BranchId And “ oCmd.Parameters.AddWithValue( _ “@BranchId”, Me.branchIdTable(CboBranch.Text))

End If‘Concatenate “Model” conditionIf CboCategory.Text <> “All” Then

strSql += “Model.CategoryId = @CategoryId And “ oCmd.Parameters.AddWithValue(“@CategoryId”, _ Me.categoryIdTable(CboCategory.Text))

End If‘Concatenate “Operational” conditionIf CboOperational.Text <> “All” Then

strSql += “Vehicle.Operational = @Operational And “ ‘Convert “Operational” textual value to integerSelect Case CboOperational.Text

Case “In Operation” operational = 0

Case “In Maintenance” operational = 1

End Select‘ Add “Operational” command parameteroCmd.Parameters.AddWithValue( _ “@Operational”, operational)

End If‘Remove trailing [ AND ] from SQL statementstrSql = strSql.Substring(0, strSql.Length - 5)

End If‘open connectionoCn.Open()‘Set connection to commandoCmd.Connection = oCn‘set Sql string to command objectoCmd.CommandText = strSql‘execute commandoRd = oCmd.ExecuteReader() ‘Fill Combo Categories - construct string array ‘to fill single row in gridWhile oRd.Read

Dim row As String() = {oRd(“LicensePlate”), _ oRd(“CategoryName”), _ ‘Convert “Available” integer value to stringAvailableText(oRd(“Available”)), _ ‘Convert “Operational” integer value to stringIIf(oRd(“Operational”) = 0, _ “In Operation”, “In Maintenance”), _ ‘Add rest of values to string arrayoRd(“ModelName”), _ oRd(“BranchName”), _ oRd(“DailyPrice”), _ oRd(“WeeklyPrice”), _ oRd(“MonthlyPrice”), _

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 504

Page 540: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

505

Listing B-12: BtnDisplay_Click Event-Handling Routine (continued)

oRd(“Mileage”), _ oRd(“Tank”), _ ‘Add rest of values to string array ‘while checking for database null valuesIIf(IsDBNull(oRd(“FirstName”)), “ “, oRd(“FirstName”)), _ IIf(IsDBNull(oRd(“LastName”)), “ “, oRd(“LastName”)), _ IIf(IsDBNull(oRd(“DocNumber”)), “ “, oRd(“DocNumber”)), _ IIf(IsDBNull(oRd(“DocType”)), “ “, oRd(“DocType”)) _ }‘Add string array to grid as single row valuesMe.DGridFleetView.Rows.Add(row)

End While‘close readeroRd.Close()oCn.Close()‘destroy objectsoRd = NothingoCmd = NothingoCn = NothingExit Sub

ErrorHandler:MsgBox(“A problem occurred “ + _“and the application can not recover! “ + _“Please contact the technical support.”)Err.Clear()

End Sub

“The user can choose not to apply filters, to apply all filters, or to apply just any number of filters. Eachfilter is transformed into a single condition in the Where part of an SQL query. Because of this, we needto construct the query dynamically. If no filter is applied, we do not need to add the Where keywordafter the Select part of the query. If there is a single condition, we need to add Where to the SQL com-mand. Filters are exclusive, and it means they need to be concatenated using the AND keyword. Since wecannot be sure which filter is the last and so as not to add any more ANDs after constructing the query,the last AND keyword is simply removed from the SQL string.”

I said, “So, while you build the Where part of the query, each time you add a condition, for example,Vehicle.Available = @Available, you always add an AND keyword to the end of the condition.This way you always end up with one AND keyword too many.”

Tim said, “Exactly. String AND plus two blank spaces amounts to a length of five; this can be seen in the line

strSql = strSql.Substring(0, strSql.Length - 5)

“This way the trailing AND is removed from the SQL query code.

“Now, since we use integers to represent the Available and Operational state in the database, we need totranslate these codes to the user. In the case of Operational, this is done directly, by using the IIf function:

IIf(oRd(“Operational”) = 0, “In Operation”, “In Maintenance”)

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 505

Page 541: Professional Refactoring in Visual Basic

Appendix B: Rent-a-Wheels Prototype Internals and Intricacies

506

“Here, 0 means In Operation and any other value means In Maintenance. But the only other value thatthis field can have in the database is 1. So, this corresponds to the values of the Maintenance column weexamined earlier on.

“With the available column, the situation is more complicated, because there are more than two valuesthat this column can have. Therefore, I used a separate method called AvailableText to translate theinteger value to a descriptive string value. The method is called using this code:

AvailableText(oRd(“Available”))

“Now, take a look at the method itself.”

Tim presented the AvailableText method shown in Listing B-13.

Listing B-13: AvailableText Method Used to Translate Integer Values of AvailableColumn to Descriptive String Values

Private Function AvailableText(ByVal available As Integer) _As String

Select Case availableCase 0

strAvailable = “Available”Case 1

strAvailable = “Hand Over”Case 2

strAvailable = “Rented”Case 3

strAvailable = “Charge”End SelectReturn strAvailable

End Function

“That is how the dynamic search SQL query is constructed and used to help the user display only thevehicles he is interested in. That way we have given an efficient tool to a user and optimized the timeused to perform each operation.”

SummaryThis more or less wraps up all the interesting details of the VB code in the prototype Rent-a-Wheelsapplication constructed for example purposes for this book. I didn’t go into the detail a of each andevery method, but you’ve had a chance to inspect all that’s of major interest, and certainly all you needto see of this application so it can serve as a test case for the refactoring demonstrated in the book.

Once again, please remember this code is presented as an illustration of legacy VB approach to program-ming. It is poorly designed and it is not using any of the VB .NET object-oriented features, but this hasbeen done on purpose. Through the refactoring process, and throughout this book, you improve thiscode and in the process discover new, better ways to design your applications.

The full code for the application is available for download at the book’s web site at www.wrox.com, and I definitely encourage you to download it (if you haven’t already) and use it to follow along as you workthrough the book.

79796bapp02.qxd:WroxPro 2/22/08 5:01 PM Page 506

Page 542: Professional Refactoring in Visual Basic

Inde

x

Index

Aabstract classes, 331–332Abstract Factory design pattern, 411–425abstract form inheritance, 390–391

form helper classes, 392–394access level, 181, 182

gradual reduction, 187public, 184reduce, 182–183

applicationsCalories Calculator, 19–22

BtnCalculate_Click method, 42–43BtnSave_Click method, 45–47calculating by gender, 28–29calculating ideal weight, 22–24conditional logic, 34–37DailyCaloriesRecommended, 35DistanceFromIdealWeight, 35frmCaloriesCalculator, 36–37gender-specific methods, 39–40IdealBodyWeight, 35LINQ, 448–451Patient class hierarchy, 37–38Patient class interface, narrowing, 32–34patient classes hierarchy, 40–42Patient Data class, 30–31patient history display, 52–55Patient-History XML File Format, 45PatientHistory XML Storage class, 53–55persistent data, 24–26, 44XML literals, 448

GUI-based application, 103Patient class, Gender property, 33Rent-a-Wheels

actors, 91assemblies, 402–403basic hygiene, 192–193BtnRent_Click Fleet View, 99–100

BtnRent_Click Rent Form, 100–101database model, 96–98desk receptionist interview, 89detached event handler eliminated, 192–193error handling, 169–171extract method, 240generic types, 364–369inheritance, 364–369introduction, 87LINQ, 454–464Magic Literals, 240maintenance personnel interviews, 90–91manager interview, 88–89method reorganization, 259–267namespaces, 402–403objects, 313–320Option Explicit and, 114–115Option Strict and, 132–135parking lot attendant interview, 90patterns, 434–437rename refactoring, 217–218Safe Rename refactoring, 217–218team, 96vehicle states, 93–94

reusable modules and, 209–212self-contained, 209–212

ArtinSoft Visual Basic Upgrade Companion, 469assemblies

binary reuseencapsulation, 375intellectual property, 376memory resources, 376modularity, 375multilanguage reuse, 377security, 376versioning, 376

compatibility between versions, 213namespaces, 377–381Rent-a-Wheels, 402–403

79796bindex.qxd:WroxPro 2/22/08 5:01 PM Page 507

Page 543: Professional Refactoring in Visual Basic

508

assembly references

assembly references, unused, removing, 191–192Assert class, 78–79Assessment tool, 469automated refactoring tools

Refactor! (Developer Express), 60smart tags, 62

Refactor! Pro (Developer Express), 61ReSharper (JetBrains), 60–61Visual Assist X (Whole Tomato), 61

automated unit testing, 71

Bbinary reuse

encapsulation, 375modularity, 375

block scope, 184blocks

Catch, 158Finally, 161–162finally block, 155

BtnCalculate_Click, 21event-handling routine, 25patient classes hierarchy, 42–43

BtnCalculate_Click method, 24after method extraction, 29Calculate and Display Distance from Ideal Weight

segment, 28Clear Old Results segment, 27Validate User Input segment, 27

BtnRent_ClickFleet View, 99–100Rent Form, 100–101

BtnSave_Click, decomposing, 48–50

Ccalculating circumference, 223–225calculating ideal weight, 22–24calculating radius, extracting code, 229Calories Calculator application

BtnSave_Click method, 45–47DailyCaloriesRecommended, 35DistanceFromIdealWeight, 35first try, 21–22frmCaloriesCalculator, 36–37

IdealBodyWeight, 35introduction, 19–20LINQ, 448–451Patient class, 37–38

conditional logic, 34–37gender-specific methods, 39–40

Patient class interface, 32–33patient classes hierarchy, 40–42

BtnCalculate_Click method, 42–43Patient Data class, 30–31

moving methods to, 31–32patient history display, 52–55Patient-History XML File Format, 45PatientHistory XML Storage class, 53–55persistence, patient personal data, 44XML literals, 448

Calories Calculator with Ideal-Body-Weightcalculation, 23–24

Camel case, 202Catch block, 158CCW (COM callable wrapper), 467child class, 334circumference calculation, 223–225circumference length calculation code,

extracting, 226–228classes

abstract, 331–332child, 334extract, 281–284hidden, 298–320inheritance, 327–329large, 298object-oriented programming, 276–277

actor, 285attributes, 285designing, 281domain classes, 285has a phrases, 286is a phrases, 286nouns, 284duplicated, 285operations, 286–288system classes, 285

parent, 334partial, 390

client-server, 308

79796bindex.qxd:WroxPro 2/22/08 5:01 PM Page 508

Page 544: Professional Refactoring in Visual Basic

509

DI (Dependency Injection) pattern

Inde

x

codeduplicated, 234–234readability, 10–11simplicity, 9–10

Code Advisor, 469code listings

BtnCalculate_Click, 21Calories Calculator first try, 21–22Calories Calculator with Ideal-Body-Weight

calculation, 23–24code smells, 7–8color, NUnit framework, 74commented code, 175comments, smell, 226compiler

conditional compilation, 472defaults, 142

component containers, 432composition, 334–337conditional compilation, 472constant refactoring, 239constructors, 276containers

component containers, 432generic, 333–334typed, 332–333

control structures, 184controls, forms, invisible, 178conversion function, inferring variable type,

123–124copy-paste as code reuse mechanism, 104copy-paste programming, 236–237CRC (class-responsibility-collaborator) cards,

288–297brainstorming sessions and, 293–297

CType function, 126–127cyclic dependencies, 384, 385–386

DDailyCaloriesRecommended method, testing, 79data class

defining for domain, 309domain logic, 309persistence logic, 310

data entry, unit testing, manual entry, 70–71

database-driven design, 102–103, 288, 299–302dead code, 173

commented code, 175definition, 174eliminating, 174–179, 176–177flavors of, 175–176smell, 174sources of, 176–179types of, 175–176unreachable code, 175unused code, 175

decision structures, 184decomposing methods, 223–234delegation, 348–350dependencies, 381

breaking dependency cycles, 383cyclic, 384, 385–386direction, 209inverting, 385

design, 7database-driven, 102–103, 299–302design rot, 7errors, quick fix, 184–185

design patternsAbstract Factory, 411–425behavioral, 409benefits of, 410consequences, 410creational, 409introduction, 408–409names, 409problem, 410solution, 410structural, 409using, 410

detached event handlers, 178Developer Express

Refactor!, 60Refactor! Pro, 61

DI (Dependency Injection) pattern, 426–434autowiring assembler, 430centralization, 433coded assembler, 430constructor-based injection, 429–430embedding, 433

79796bindex.qxd:WroxPro 2/22/08 5:01 PM Page 509

Page 545: Professional Refactoring in Visual Basic

DI (Dependency Injection) pattern (continued)IoC (Inversion of Control), 426metadata assembler, 431modular architecture, 433POCO programming model, 433property-based injection, 429–430Rent-a-Wheels, 434–437support, 433testing, 433

DirectCast, 127–132domain

data class, defining, 309persistence code, 311–313

double type variables, 120downcast, 127downloads, NUnit, 72duck typing, 136–137duplicated code, 234–234

sources, 235–236duplication, inheritance and, 350–352dynamic behavior, resetting, 138dynamic typing, static typing and, 135dynamically typed languages, 109

Eelements

obsolete, 179unused, importing, 178

encapsulation, 173, 180field (Refactor!), 274objects and, 272–274

entities, relationships and, 297–298entity classes, attributes, 460Err object, 166error code

replacing with exception type, 164–169smell, 154

error filtering, 154–155error handling

introduction, 147legacy, 148–150

smell, 153.NET and, 155–156Rent-a-Wheels application, 169–171structured, 148, 150–153

unstructured and, 153

Err.Raise method, 168event-driven programming, 103–104event handlers, detached, 178event-handling blindness smell, 232exception handling, 147

hierarchies, 151upgrading, 481

exceptions, as types, 154excplicit imports, 173ExpectedException attribute, 81–82explicit imports, 187–181, 374explicit variable-type conversion, 126–127external assemblies, inferring variable type,

124–125extract class, 281–284extract method, 221–223extract namespace, 382–383extract superclass, 352–355

Ffield, move field, 291–293field refactoring, 274filtering, error filtering, 154–155Finally block, 161–162finally block, 155forms

abstract form inheritance, 390–391controls, invisible, 178helper classes, abstract form inheritance,

392–394inheritance, abstract, 392–394Inherited Form, 390–391

functional testing, 472–473harness, implementing, 473–476

functionsIsNumeric, 22type-conversion, 125–132

Ggarbage collection, 279

reference counting garbage collector, 279tracing garbage collector, 280

generic containers, 333–334, 479–481generic types, 332

Rent-a-Wheels, 364–369

510

DI (Dependency Injection) pattern (continued)

79796bindex.qxd:WroxPro 2/22/08 5:01 PM Page 510

Page 546: Professional Refactoring in Visual Basic

generics, 360GetException method, 167GUI-based application, 103

Hhidden classes, 298–320Hungarian notation, 204

IIdealBodyWeight method, testing, 77–78implementation hiding, 220–223implicit narrowing conversions, 125implicit type declaration, 118implicit variable declaration, 113importing

dependencies in system, 188–181explicit imports, 187–181, 374unused elements, 178

Imports statement, 373–374inferring initial type of variable, 118–119

conversion function, 123–124inferring variable type, 122–125

external assemblies and, 124–125information hiding, 220–223inheritance, 324–327, 478–479

class inheritance, 327–329delegation, 338–342, 348–350duplication and, 350–352forms, abstract, 390–391, 392–394interface, 327–329Rent-a-Wheels, 364–369

Inherited Form, 390–391inline temp refactoring, 253–255interface inheritance, 327–329interfaces, 208

objects and, 272published, 209

inverting dependencies, 385invisible controls on forms, 178IsNumeric function, 22item templates, 143–144

JJetBrains ReSharper, 60–61JUnit framework, 72

Kkeywords

MustInherit, 331New, 276Shared, 276When, 158

Llanguages

dynamically typed, 109statically typed, 109

large class, 298late binding, 136late bound calls, 117–118legacy code upgrades, 468

static typing, 477legacy error handling, 148–150

smell, 153LINQ (Language Integrated Query),

440, 445Calories Calculator, 448–451queries, example, 446Rent-a-Wheels, 454–464

LINQ to SQL, 451–454literals

inferring variable type, 122–123magic literals, 236, 237–239

local variables, 179method extraction, 244type inference, 439–440

long method, 225Long type, 119–120looping, retaining, 250–253looping variables, 250

Mmagic literals, 236, 237–239

replacing with constant, 237–239memory, garbage collection, 279

reference counting garbage collector, 279tracing garbage collector, 280

messages, object-oriented programming,280–281

method extraction, 243local variables, 244

511

method extraction

Inde

x

79796bindex.qxd:WroxPro 2/22/08 5:01 PM Page 511

Page 547: Professional Refactoring in Visual Basic

methodsambiguous parameter types, 128BtnCalculate_Click, 24decomposing, 223–234Err.Raise, 168extract, 221–223GetException, 167long, 225move, 289–290obsolete, 129overloading, 128pull up, 357–360queries as, 256–258reorganization, 258–259

Rent-a-Wheels, 259–267migration

ArtinSoft Visual Basic Upgrade Companion, 469Assessment tool, 469automation, 467Code Advisor, 469Microsoft.VisualBasic.Compatibility

assembly, 469testing harness, 472upgrade and, 466Visual Basic Upgrade Wizard, 469

misconceptions of refactoring, 12–13monolithic application, 470move field, 291–293move method, 289–290move type to file refactoring, 388–390MustInherit keyword, 331

Nnamespaces, 371–372

extract, 382–383large, 377naming guidelines and, 372nested, 372non-coherent, 378Rent-a-Wheels, 402–403root namespace name, changing, 372–373

naming guidelines, 201–202Camel case, 202capitalization styles, 202–203consistency in nouns and verbs, 205

easily pronouncable and memorizable, 205Hungarian notation, 204namespaces and, 372one word to represent one meaning, 205Pascal case, 202simple, 203–204Upper case, 202vocabulary as source for identifier names, 205

narrowing conversions, 125NCover, 84nested namespaces, 372.NET

error handling and, 155–156Option Explicit and, 110–111Option Strict and, 110–111

New keyword, 276NMock, 83non-coherent namespace, 378NUnit framework

Assert class, 78–79color, 74Console Error, 74Console Out, 74DailyCaloriesRecommended method, 79Errors and Failures, 73exceptions, 81–82ExpectedException attribute, 81–82IdealBodyWeight method, 77–78installing, 72–74introduction, 72JUnit framework, 72Log, 74Setup attribute, 79–80TearDown attribute, 79–80test project, creating, 74–76test runners, 72TestFixture, 76–77Tests Not Run, 73Trace, 74writing tests, 77–78

NUnitForms, 83

Oobject identity, 277–278object-mocking framework, 83–84

512

methods

79796bindex.qxd:WroxPro 2/22/08 5:01 PM Page 512

Page 548: Professional Refactoring in Visual Basic

object-oriented programmingclasses, 276–277

abstract, 331–332actor, 285attributes, 285designing, 281domain classes, 285has a phrases, 286is a phrases, 286nouns, 284duplicated, 285operations, 286–288system classes, 285

composition, 334–337CRC (class-responsibility-collaborator) cards,

288–297database-driven design, 299–302design principles

acyclic dependencies principle, 387favor object composition over class inheritance,

331open-closed, 215–217program to an abstraction, 331reuse-release equivalence, 378–379single responsibility, 294–297

entities, relationships and, 297–298garbage collection, 279

reference counting garbage collector, 279tracing garbage collector, 280

generic types, 332inheritance, 324–327introduction, 272messages, 280–281moving from procedural, 477–478object identity, 277–278object lifetime, 279polymorphism, 329–331procedural design and, 302–308reuse-release equivalence, 378–379root object, 278–279single responsibility, 294–297

objects, 272encapsulation and, 272–274interfaces, 272lifetime, 279

Rent-a-Wheels, 313–320root object, 278–279state retention, 276upcasting object declaration, 413

obsolete elements, 179On Error Goto label, 148, 159–162

replacing with Try-Catch-Finally, 159–162On Error Resume Next statement, 149–150

replacing with Try-Catch-Finally, 162–163On Error statement, 147

replacing with Try-Catch-Finally, 158–159Option Explicit, 110

activating, 141–144.NET and, 110–111refactoring and, 112–114relaxed code and, 111–115Rent-a-Wheels and, 114–115variable declaration and, 111–112

Option Strict option, 110activating, 141–144late bound calls, 117–118.NET and, 110–111off setting, 116–119relaxed code, 115Rent-a-Wheels and, 132–135

ORM (object-relational mappings), 451overexposure, 179–180, 186–187

sources of, 182–186overriding, 327

Pparameterized constructor, 479parent class, 334partial classes, 390Pascal case, 202permissive code, 116–119persistence

calories calculator, validating patient personaldata, 44

implementing, 43–52POCO (Plain Ol’ CLR Object), 432polymorphism, 329–331

Abstract Factory design pattern, 412primitive types, 278print system, 342–360

513

print system

Inde

x

79796bindex.qxd:WroxPro 2/22/08 5:01 PM Page 513

Page 549: Professional Refactoring in Visual Basic

problem domaininformation gathering, 198–199interactions

use cases, 200user stories, 200–201

prototype, 201vocabulary, 199–200

procedural design, moving to object-oriented,302–308, 477–478

Project Properties, options, 141–142properties, queries as, 256–258public access level, 184public interfaces, 208

modifying, 212–214published interfaces, 209pull up method, 357–360

Qqueries. See also LINQ (Language

Integrated Query)as methods, 256–258as properties, 256–258syntax, 445

RRAD (Rapid Application Development), 104,

208, 308radius, calculating, extracting code, 229read coordinates code, extracting, 229–233Refactor! (Developer Express), 60

constant refactoring, 239Ctrl key, 63cut and paste, 63encapsulate field, 274extract method refactoring, 233–234inline temp, 255move declaration near reference, 247move initialization to declaration, 248–249move type to file, 388–390progress indicator, 67refactorings supported, 67–69replace temp with query, 258right-clicking mouse, 62Safe Rename refactoring, 214–217

smart tags, 62split temporary variable, 251–253VB user interface

action hints, 64big hints, 64linked identifiers, 66markers, 64shortcut hints, 64target pickers, 66

XML literals, 444–445Refactor! Pro (Developer Express), 61refactoring

benefits of, 9–11constant, 239inheritance and, 342–345legacy issues, 16misconceptions of, 12–13Option Explicit and, 112–114process overview, 4–5Replace Temp with Query, 247

refactorsBreak Monolith, 471convert procedural design to objects, 307–308eliminate dead code, 176–177encapsulate field, 275explicit imports, 374extract class, 281–284extract interface, 346–348extract method, 221–223extract namespace, 382–383extract superclass, 352–355inline temp, 254–255move class to namespace, 380move declaration near reference, 245–246move element to more enclosed region,

185–186move field, 291–293move initialization to declaration, 248–249move method, 289–290move type to file, 388–390Option Explicit, set on, 113–114Option Strict on, 131–132pull up method, 357–360reduce access level, 182–183remove unused references, 192

514

problem domain

79796bindex.qxd:WroxPro 2/22/08 5:01 PM Page 514

Page 550: Professional Refactoring in Visual Basic

renaming, 206–207replace complex VB queries with LINQ, 447replace error code with exception type, 164–166replace fully qualified names with explicit imports

motivation, 188–189replace general-purpose reference with

parameter type, 361–363replace inheritance with delegation, 338–342replace magic literal with constant, 237–239replace on error construct with Try-Catch-Finally

motivation, 156–157replace programmatic data layer with LINQ to

SQL motivation, 453–454replace row with data class, 299–302replace temp with query, 257–258replace XML string literals with XML literal,

442–443replacing row with data class, 299–302safe rename, 210–212set option explicit on (enforce variable

declaration), 113–114set option explicit on (enforce variable type

declaration and explicit type con), 131–132split temporary variable, 251–253

reference counting garbage collector, 279reference refactoring, variable declaration,

244–246relationships, entities and, 297–298renaming, 206–207

Rent-a-Wheels, 217–218safe, 210–212

Rent-a-Wheelsabstract forms, 394–396actors, 91

steps, 92assemblies, 402–403basic hygiene, 192–193BtnRent_Click

Fleet View, 99–100Rent Form, 100–101

database, model, 96–98desk receptionist interview, 89detached event handler eliminated, 192–193error handling, 169–171extract method, 240

forms, abstract, 394–396generic types, 364–369inheritance, 364–369introduction, 87LINQ, 454–464Magic Literals, 240maintenance personnel interviews, 90–91manager interview, 88–89method reorganization, 259–267namespaces, 402–403objects, 313–320Option Explicit and, 114–115Option Strict and, 132–135parking lot attendant interview, 90patterns, 434–437rename refactoring, 217–218Safe Rename refactoring, 217–218team, 96vehicle states, 93–94

Replace Temp with Query refactor, 247replacing row with data class, 299–302ReSharper (JetBrains), 60–61retaining looping, 250–253return parameter, ignored procedure return

parameter, 179return values, ignored procedure return

value, 179reusable assemblies, 209–212right-clicking mouse, Refactor!, 62root namespace name, changing, 372–373root object, 278–279

Ssafe rename refactor, 210–212Safe Rename refactoring (Refactor!), 214–217scope, 181

block scope, 184gradual reduction, 187levels of, 182

Setup attribute, 79–80shadowing, 327Shared keyword, 276smells

comments, 226cyclic dependencies, 384

515

smells

Inde

x

79796bindex.qxd:WroxPro 2/22/08 5:01 PM Page 515

Page 551: Professional Refactoring in Visual Basic

smells (continued)data class, 286database-driven design, 288dead code, 174duplicated code, 235error code, 154event-handling blindness, 232implicit imports, 372implicit narrowing conversions, 125implicit type declaration, 118implicit variable declaration, 113large class, 298large namespace, 377legacy error handling, 153long method, 225magic literals, 236monolithic application, 470non-coherent namespace, 378overburdened temporary variable, 249–250overexposure, 179–180procedural design, 302refused bequest, 337–338superfluous temporary variable, 254unrevealing names, 201–202unused references, 191using fully qualified names outside the imports

section detecting the smell, 187–188XML string literals, 441

software, 5–6change and, 6–7design, 6design rot, 7

source files, options, 143spaghetti code, 220split temporary variable, 251–253

Refactor!, 251–253SQL (Structured Query Language), LINQ to SQL,

451–454state retention, objects, 276static behavior, resetting, 138static typing, 477statically typed languages, 109statically typed wrappers, 138–140statically typing, dynamic typing and, 135string variables, 121

subroutines, 220superclass, extract, 352–355System.Exception class, 151

TTearDown attribute, 79–80temporary variables, 243–244

split refactoring, 249–253superfluous, 254

test runners, NUnit, 72TestDriven.NET, 83TestFixture, 76–77testing. See also unit testing

functional testing, 472–473harness, 473–476

testing harness, migrated code, 472three-tiered architecture, 308tools

Refactor! (Developer Express), 61–67Refactor! Pro (Developer Express), 61ReSharper (JetBrains), 60–61Visual Assist X (Whole Tomato), 61

tracing garbage collector, 280transformations, 8

automating, 8–9Try-Catch-Finally blocks, 147

replacing On Error Goto label, 159–162replacing On Error Resume Next, 162–163replacing On Error statement, 158–159

TryCast, 127–132type-conversion functions, 125–132type declaration, implicit, 118type inference, local variables, 439–440typed containers, 332–333

Uunit testing

ad hoc, 71Assert class, 78–79automated, 71DailyCaloriesRecommended method, 79data entry, manual, 70–71exceptions, 81–82ExpectedException attribute, 81–82

516

smells (continued)

79796bindex.qxd:WroxPro 2/22/08 5:01 PM Page 516

Page 552: Professional Refactoring in Visual Basic

framework, 70–72IdealBodyWeight method, 77–78introduction, 69–70NCover, 84NUnit framework, 72NUnitForms, 83Setup attribute, 79–80TearDown attribute, 79–80test project, creating, 74–76TestDriven.NET, 83TestFixture, 76–77writing tests, 77–78

unreachable code, 175unused assembly references, 173

removing, 191–192unused code, 175unused elements, importing, 178upcasting object declaration, 413upgrading

legacy code upgrades, 468migration and, 466

Upper case, 202

Vvariables

collecting, 250–253declaration, reference refactoring, 244–246declaring

implicit, 113Option Explicit and, 111–112

double, 120dynamically changing type, 117inferring type, 122–125

conversion function, 123–124external assemblies, 124–125

initial type, inferring, 118–119late bound calls, 117–118long type, 119–120looping, 250string, 121temporary, 243–244

split refactoring, 249–253type conversion, explicit, 126–127

version control, 84–85Visual Assist X (Whole Tomato), 61Visual Basic

history, 14legacy code, 15legacy issues, 14legacy language elements, 15–16legacy programming, 15–16VB 6 and VB .NET, 467–468

Visual Basic Upgrade Wizard, 469

Wwait for user to close code,

extracting, 229When keyword, 158Whole Tomato’s Visual Assist X, 61

XXML (eXtensible Markup Language)

axis properties, 444comments, 481–482literals, 440–444

Calories Calculator, 448extract to resource, 444–445string literals, 441

strings, replace with literals, 443–444

517

XML (eXtensible Markup Language)

Inde

x

79796bindex.qxd:WroxPro 2/22/08 5:01 PM Page 517

Page 553: Professional Refactoring in Visual Basic

79796bindex.qxd:WroxPro 2/22/08 5:01 PM Page 518

Page 554: Professional Refactoring in Visual Basic

Get more fromWrox.

Available wherever books are sold or visit wrox.com

978-0-470-18757-9 978-0-470-19137-8 978-0-470-19136-1

Gorroffr

teetGmoom

ommorWWr

erreoorxooxrroorroffr moom rWWr .xooxrro

9-75781-074-0-879 9 -879 879 8-73191-074-0- 8731910740 1-63191-074-0-879

obreverehwelbaliavAAv oc.xorwtisivrodloseraskoo mo

79796badvert.qxd:WroxPro 2/22/08 5:01 PM Page 519

Page 555: Professional Refactoring in Visual Basic

Take your library wherever you go.Now you can access more than 200 complete Wrox booksonline, wherever you happen to be! Every diagram, description,screen capture, and code sample is available with your subscription to the Wrox Reference Library. For answers when and where you need them, go to wrox.books24x7.com andsubscribe today!

Programmer to ProgrammerTM

• ASP.NET • C#/C++ • Database • General • Java• Mac• Microsoft Office

• .NET • Open Source • PHP/MySQL • SQL Server • Visual Basic • Web• XML

Find books on

www.wrox.com

79796badvert.qxd:WroxPro 2/22/08 5:01 PM Page 520