34,79 €
Software projects start as brand-new greenfield projects, but invariably become muddied in technical debt far sooner than you’d expect. In Refactoring with C#, you'll explore what technical debt is and how it arises before walking through the process of safely refactoring C# code using modern tooling in Visual Studio and more recent C# language features using C# 12 and .NET 8. This book, written by a Microsoft MVP, will guide you through the process of refactoring safely through advanced unit testing with XUnit and libraries like Moq, Snapper, and Scientist .NET. You'll explore maintainable code through SOLID principles and defensive coding techniques made possible in newer versions of C#. You'll also find out how to run code analysis and write custom Roslyn analyzers to detect and resolve issues unique to your code.
The nature of coding is changing, and you'll explore how to use AI with the GitHub Copilot Chat to refactor, test, document, and generate code before ending with a discussion about communicating technical debt to leadership and getting organizational buy-in to refactor your code in enterprise organizations and in agile teams.
By the end of this book, you'll understand the nature of refactoring and see how you can safely, effectively, and repeatably pay down the technical debt in your application while adding value to your business.
Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:
Seitenzahl: 539
Veröffentlichungsjahr: 2023
Refactoring with C#
Safely improve .NET applications and pay down technical debt with Visual Studio, .NET 8, and C# 12
Matt Eland
BIRMINGHAM—MUMBAI
Copyright © 2023 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Group Product Manager: Kunal Swant
Associate Publishing Product Manager: Debadrita Chatterjee
Senior Editor: Esha Banerjee
Technical Editor: Jubit Pincy
Copy Editor: Safis Editing
Project Coordinator: Manisha Singh
Proofreader: Safis Editing
Indexer: Rekha Nair
Production Designer: Ponraj Dhandapani
Marketing Co-ordinator:Sonia Chauhan
First published: November 2023
Production reference: 1271023
Published by Packt Publishing Ltd.Grosvenor House 11 St Paul’s SquareBirmingham B3 1RB, UK
ISBN 978-1-83508-998-9
www.packtpub.com
To my dear wife, Heather, who encouraged me to dream big and supported me while I did so.
To our dads, who we wish could be here to see this.
To all those I have had the privilege to teach, mentor, manage, and inspire – and to those I’ve yet to impact: may your learning journeys be amazing.
Matt
Matt Eland, aka Integerman, has, like me, been writing software for over three decades. Upon delving this book, it became clear to me that he has seen some things. It’s hard to have a lengthy career in software development and not encounter the grim effects of legacy code and technical debt. What separates Matt from many other developers, though, is that rather than shying away from the challenges these things pose, he’s developed a rich set of techniques to combat them. It is these techniques and skills that he shares with you in Refactoring with C#.
Technical debt is a ubiquitous presence in virtually every codebase. It’s a metaphor for the small (and sometimes not so small) shortcuts, messes, and design mismatches left behind as the software evolves, often due to the software’s failure to adapt to changing external factors. Left unchecked, these problems can slow productivity to a crawl, and refactoring is the main tool developers have to deal with this problem. It’s a vital skill every developer should have.
This book has a great conversational style. Reading it is like having Matt sitting next to you, walking you through the examples. Many of them start out with a fair bit of complexity, but Matt does a great job of breaking this complexity down into understandable chunks, which he then demonstrates how to break down further through refactoring techniques.
Another technique Matt employs with great effectiveness is his use of tools and screenshots to demonstrate the effective usage of tools. Most of the book leverages Visual Studio, but there’s coverage for VS Code and other tools as well. Wherever a tool can be used to assist with a refactoring technique, Matt shows how to do so in a clear, concise manner, with effective use of screenshots. As they say, a picture is worth a thousand words!
The breadth of the book you’re holding is also impressive. In addition to demonstrating refactoring techniques that involve rearranging a few lines of code, Matt provides invaluable tips for effectively communicating the concepts of technical debt and the importance of refactoring to your managers and business stakeholders. There’s content dedicated to keeping your code up-to-date with the latest software changes. He doesn’t just mention that refactoring benefits from having automated tests, he even dedicates a couple of chapters to testing techniques and tools!
Whether you’re a novice developer or have been coding for decades, I’m confident you’ll find some useful tips and techniques in Refactoring with C#. I know I did, despite having taught refactoring to .NET developers for many years.
Steve “ardalis” Smith Principal Architect, NimblePros. Pluralsight author. 20x Microsoft MVP.
Matt Eland is a Microsoft MVP in Artificial Intelligence (AI) who has been working with .NET since 2001. Matt has served as a senior engineer, software engineering manager, and .NET programming instructor. He is currently an AI specialist and senior consultant at Leading EDJE near Columbus, Ohio, where he helps companies with their software engineering and data science needs using C# and related technologies. Matt speaks and writes in his community and co-organizes the Central Ohio .NET Developers Group while pursuing a master’s degree in data analytics. You can find more of Matt’s work at MattEland.dev.
Matthew, Brad, Calvin, Sam, Steve, and Esha: thank you for your hard work in refactoring this book. Debadrita, thank you for pitching this idea to me. To Heather, our families, Wren, Sadukie, Matt, Angelia, my fellow EDJErs, the Dads, and countless others: thank you for your encouragement and support. Microsoft, thank you for 20+ years of C# and giving us the tools we need to build great things. Finally, I am grateful to God for granting me the skills, knowledge, time, and health to write this.
Brad Knowles is a Cloud Application Architect (CAA) with AWS Professional Services, specializing in migrating and optimizing .NET workloads running in the cloud. With over two decades of experience in the software industry, Brad has written applications for several industries, including supply chain and healthcare. During that time, he has deployed from single on-premises web servers all the way up to multiple containerized microservices. As an architect, his primary goal is to build resilient systems, minimize complexity, and balance the trade-offs between the two. He shares his knowledge at local meetups and conferences and blogs about .NET and architecture-related topics at https://bradknowles.com.
Calvin A. Allen is highly involved in the tech community. He is a recognized Microsoft MVP for his contributions to the developer community, which include mentoring, writing technical articles/blog posts, and organizing tech events. Calvin is also a contributor to various open-source projects and is passionate about sharing his knowledge and expertise with others through his blog, Coding with Calvin, which can be found at https://www.codingwithcalvin.net.
Matthew D. Groves is a guy who loves to code. It doesn’t matter whether it’s C#, jQuery, or PHP: he’ll submit pull requests for anything. He has been coding professionally ever since he wrote a QuickBASIC point-of-sale app for his parent’s pizza shop back in the 90s. He currently works for Couchbase, helping developers in any way he can. His free time is spent with his family, watching the Reds, and getting involved in the developer community. He is the author of AOP in .NET, co-author of Pro Microservices in .NET, a Pluralsight author, and a Microsoft MVP.
Samuel Gomez has worked in software development for 15+ years (mostly Microsoft technologies) and he thoroughly enjoys the problem-solving aspect of the work. In recent years, he has become passionate about AI and machine learning technologies and how they can be applied to different aspects of our lives. When not coding, he enjoys spending time with his family, soccer (watching, playing, and coaching), video games, and watching movies.
Software projects quickly go from greenfield paradises to brownfield wastelands filled with legacy code and technical debt. Every engineer will encounter projects that are more difficult than they should be due to existing technical debt. This book covers the process of refactoring existing code into more maintainable forms.
In Refactoring with C#, we focus on using modern C# and Visual Studio features to safely pay down technical debt in a sustainable way – while continuing to deliver value to the business.
This book is for two distinct types of readers.
The first is junior and mid-level C# developers in the first few years of their careers. This book will teach you the programming techniques and mentalities needed to advance in your career. You’ll learn how to safely refactor your code and find new ways of improving the overall structure of your code.
The second type of reader is the software engineer or engineering manager dealing with a particularly troublesome codebase or a project or organization resistant to refactoring. This book will help you make the case for refactoring, ensure you can do it safely, and give you alternatives to all-or-nothing approaches of complete rewrites.
This book also features a number of libraries and language features you may not have encountered or thought about recently. I hope that this book gives you new perspectives, tools, and techniques that will aid you as you refactor your code and build a better codebase.
Chapter 1, Technical Debt, Code Smells, and Refactoring, introduces the reader to the concept of technical debt and the things that cause it. The chapter covers legacy code and its impact on the development process and code smells that help you find it. The chapter closes with the idea of refactoring, which is the focus of the rest of the book.
Chapter 2, Introduction to Refactoring, illustrates the process of refactoring C# code in Visual Studio by taking a sample piece of code and progressively refining it with built-in refactorings and custom actions.
Chapter 3, Refactoring Code Flow and Iteration, focuses on refactoring individual lines and blocks of code. We focus on program flow control, object instantiation, handling collections, and using LINQ appropriately.
Chapter 4, Refactoring at the Method Level, expands the scope of the previous chapter by refactoring methods and constructors to more maintainable forms. Maintaining consistency within the class and building small, maintainable methods is a core focus.
Chapter 5, Object-Oriented Refactoring, takes the ideas of the previous refactoring chapters and applies them at the entire class level. This shows how introducing interfaces, inheritance, polymorphism, and other classes in general can lead to better patterns of code and more maintainable software systems.
Chapter 6, Unit Testing, serves as an introduction to unit testing in C#, moving quickly from the idea of a unit test to a tour of how to write one in xUnit, NUnit, and MSTest. We also cover parameterized tests and unit testing best practices.
Chapter 7, Test-Driven Development, introduces the reader to test-driven development and red/green/refactor by following the TDD process to improve code and enact refactorings. Code generation quick actions are also discussed here.
Chapter 8, Avoiding Code Anti-Patterns with SOLID, focuses on what makes code good or bad and how common patterns such as SOLID, DRY, and KISS can help make your code more resistant to technical debt.
Chapter 9, Advanced Unit Testing, covers a variety of testing libraries for data generation, mocking, pinning existing behavior, and safely making changes with A/B tests. We cover Bogus, Fluent Assertions, Moq, NSubstitute, Scientist .NET, Shouldly, and Snapper.
Chapter 10, Defensive Coding Techniques, shows off a wide range of C# language features that can make your code more reliable and resistant to defects. This chapter covers nullability, validation, immutability, record classes, pattern matching, and more.
Chapter 11, AI-Assisted Refactoring with GitHub Copilot, introduces the reader to the latest AI tooling in Visual Studio with GitHub Copilot Chat. This chapter shows the reader how to use GitHub Copilot Chat to generate code, give refactoring suggestions, write draft documentation, and even help test your code. We also stress on data privacy concerns and ways of guarding your company’s intellectual property.
Chapter 12, Code Analysis in Visual Studio, highlights the code analyzers built into modern .NET by showing how code analysis profiles can help detect issues in your code. We also explore code metrics and prioritize technical debt areas using those metrics. The chapter closes by looking at the SonarCloud and NDepend tools, which can help track technical debt over time.
Chapter 13, Creating a Roslyn Analyzer, introduces the idea of custom Roslyn Analyzers that can detect issues in your code. The chapter guides the reader through writing their first analyzer, unit testing it with RoslynTestKit, and deploying it using a Visual Studio extension.
Chapter 14, Refactoring Code with Roslyn Analyzers, shows how Roslyn Analyzers can also fix the issues they detect. The chapter picks up where the previous one left off by expanding the analyzer to provide a code fix. We then discuss packaging analyzers in NuGet packages and publishing them on NuGet.org or other NuGet feeds.
Chapter 15, Communicating Technical Debt, covers the systematic process of tracking and reporting technical debt in a way that business leaders can understand. We cover many common obstacles to refactoring and building a culture of trust and transparency where business management can understand the risks that technical debt represents.
Chapter 16, Adopting Code Standards, talks about the process of determining code standards that are appropriate for your development team and getting developer buy-in. The chapter covers code styling in Visual Studio, code cleanup profiles, and sharing EditorConfig files to promote consistent style choices across your team.
Chapter 17, Agile Refactoring, closes the book with a discussion of refactoring in agile environments and the unique challenges agile can pose to refactoring. We talk about ways of prioritizing and paying down technical debt inside of agile sprints. The chapter also covers larger projects, such as upgrades and rewrites, and ways to help those larger projects succeed.
The ideal reader should be familiar with the C# programming language and the Visual Studio IDE. Knowledge of object-oriented programming, classes, and LINQ will be particularly helpful.
Software/hardware covered in the book
Operating system requirements
Visual Studio 2022 v17.8 or higher
Windows
.NET 8 SDK
This book works with any edition of Visual Studio from 2022 v17.8 onward, including Visual Studio Community. You can download Visual Studio from https://visualstudio.microsoft.com/downloads/.
The latest version of the .NET 8 SDK can be downloaded from https://dotnet.microsoft.com/en-us/download/dotnet/8.0.
If you are using the digital version of this book, we advise you to type the code yourself or access the code from the book’s GitHub repository (a link is available in the next section). Doing so will help you avoid any potential errors related to the copying and pasting of code.
Many chapters feature step-by-step instructions that you can follow along with by using the beginning code for a chapter to produce the code featured in the chapter’s final code folder. You can also keep an eye on other code you work with as you read the book and think about how the topics apply to that code. However, you may want to refrain from applying your refactoring techniques to real-world codebases until you’ve read the chapters covering safely testing your code.
You can download the example code files for this book from GitHub at https://github.com/PacktPublishing/Refactoring-with-CSharp. If there’s an update to the code, it will be updated in the GitHub repository.
We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!
There are a number of text conventions used throughout this book.
Code in text: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: “Let’s look again at the IFlightUpdater interface from earlier.”
A block of code is set as follows:
public interface IFlightRepository { FlightInfo AddFlight(FlightInfo flight); FlightInfo UpdateFlight(FlightInfo flight); void CancelFlight(FlightInfo flight); FlightInfo? FindFlight(string id); IEnumerable<FlightInfo> GetActiveFlights(); IEnumerable<FlightInfo> GetPendingFlights(); IEnumerable<FlightInfo> GetCompletedFlights(); }When we wish to draw your attention to a particular part of a code block, the relevant lines or items are set in bold:
public interface IFlightUpdater { FlightInfo AddFlight(FlightInfo flight); FlightInfo UpdateFlight(FlightInfo flight); void CancelFlight(FlightInfo flight); }Any command-line input or output is written as follows:
Assert.Equal() Failure Expected: 60 Actual: 50Bold: Indicates a new term, an important word, or words that you see onscreen. For instance, words in menus or dialog boxes appear in bold. Here is an example: “Click Next, then give your test project a meaningful name and click Next again.”
Tips or important notes
Appear like this.
Feedback from our readers is always welcome.
General feedback: If you have questions about any aspect of this book, email us at [email protected] and mention the book title in the subject of your message.
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/support/errata and fill in the form.
Piracy: If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.
Once you’ve read Refactoring with C#, we’d love to hear your thoughts! Please click here to go straight to the Amazon review page for this book and share your feedback.
Your review is important to us and the tech community and will help us make sure we’re delivering excellent quality content.
Thanks for purchasing this book!
Do you like to read on the go but are unable to carry your print books everywhere?
Is your eBook purchase not compatible with the device of your choice?
Don’t worry, now with every Packt book you get a DRM-free PDF version of that book at no cost.
Read anywhere, any place, on any device. Search, copy, and paste code from your favorite technical books directly into your application.
The perks don’t stop there, you can get exclusive access to discounts, newsletters, and great free content in your inbox daily
Follow these simple steps to get the benefits:
Scan the QR code or visit the link belowhttps://packt.link/free-ebook/9781835089989
Submit your proof of purchaseThat’s it! We’ll send your free PDF and other benefits to your email directlyIn the first part of the book, we’ll discuss the nature of technical debt, code smells, and refactoring. We’ll focus on the mechanical process of refactoring C# code in Visual Studio.
Throughout this part, you’ll learn how to safely alter the form of your code without changing its functionality. We’ll cover high-level concepts and then walk through refactoring individual lines of code. After this, we’ll zoom out to refactor entire methods and see how they interact with each other. Finally, we’ll look at some object-oriented approaches to refactoring that can help truly reshape your code by altering how classes interact with each other.
This part of the book can either be read as a traditional book or used as a step-by-step tutorial for refactoring the starting code found in each chapter.
This part contains the following chapters:
Chapter 1, Technical Debt, Code Smells, and RefactoringChapter 2, Introduction to RefactoringChapter 3, Refactoring Code Flow and IterationChapter 4, Refactoring at the Method LevelChapter 5, Object-Oriented RefactoringNew software projects start out clean and optimistic, but quickly grow in complexity and difficulty to maintain until the code is difficult to understand, brittle to change, and impossible to test.
If you’ve worked with code for any length of time, chances are you’ve come across code like this. In fact, if you’ve been in development for even a little bit of time, it’s likely you’ve written code you now regret.
It could be that the code is hard to read or understand. Maybe the code is inefficient or prone to errors. Perhaps the code was built under a certain set of business assumptions that later changed. Maybe the code simply no longer conforms to the standards you and your team have agreed to. Whatever the reason, bad code feels like it is practically everywhere in codebases of any significant size or age.
This code litters our software projects and reduces our development speed, causes us to introduce bugs, and generally makes us less happy and productive as software engineers.
In this book, we’ll talk about how technical debt arises and what we can do about it through the process of refactoring, guided by tests and code analysis.
In this chapter, we’re going to cover the following main topics:
Understanding technical debt and legacy codeIdentifying code smellsIntroducing refactoringWhile computer science education, books, tutorials, and online courses all focus on creating new projects from scratch, the reality is that almost all development jobs you’ll have will center around understanding, maintaining, and expanding pre-existing code that may not meet your current standards.
This pre-existing code is referred to as legacy code. You almost always inherit some amount of legacy code when joining a new project. This can be a large amount of code for pre-existing projects or a smaller set of libraries your code must work with.
There are many different definitions of the term legacy code. One that stands out to me from my readings is Michael C. Feather’s definition, in Working Effectively with Legacy Code, that legacy code is code without tests.
While I like Michael’s definition and believe testing is critically important, as we’ll see in Part 2 of this book, I personally define legacy code as follows:
Legacy code refers to any pre-existing code that would be implemented significantly differently were it rewritten today.
One key factor in legacy code is that it is code you don’t currently fully understand and as a result, its presence causes some degree of anxiety and apprehension.
This anxiety you feel when maintaining old systems is a prime symptom of something called technical debt.
Simply put, technical debt is the negative effect of legacy code on future development efforts.
In other words, legacy code has a certain amount of inherent risk that bad things will happen when the code is modified. These bad things could be bugs that are introduced due to the brittleness of the pre-existing code (or our lack of understanding of it), slower development speed, or even catastrophic issues such as critical bugs or security breaches from out-of-date security practices or deprecated dependencies.
What’s worse is that technical debt will only grow over time – particularly if left unchecked.
Before we move on, I want to address a common point of confusion I see in organizations: technical debt is not the same thing as bad code.
Certainly, some of the technical debt we have in our systems may be simply poor-quality code. It could be that an inexperienced developer wrote it and didn’t properly benefit from code review by other developers. Sometimes, projects are in a rush and the team didn’t have time to write the code properly to begin with, and never got to go back and clean it up.
Sometimes, “quick and dirty” code written for prototypes makes it into production applications when “throwaway prototypes” get hastily promoted to actual production applications, as we’ll explore in Chapter 15: Communicating Technical Debt.
Of course, there are other causes of technical debt as well.
Sometimes, the development team is under the impression that they are building software to accomplish a specific task and then that task changes as business needs evolve and new information is discovered. In these cases, teams often don’t start over with the code they were writing. They simply evolve the old code to suit the new task at hand. The result is code that works but isn’t ideally suited for the new task.
This change in requirements is normal and even expected in software development environments. Modern software development occurs in an agile manner where requirements and plans naturally evolve over time and understanding them up-front is virtually impossible.
Even if development teams understood requirements perfectly and wrote perfect code, this code will eventually become a form of technical debt due to the changing nature of software engineering.
In software development, tools and libraries change over time. At the time of writing, .NET 8 and C# 12 are the latest ways to run C# code, but these technologies will go out of support at some point in the future only to be replaced by newer versions.
Even entire ways of thinking about software can change. Over the last twenty years, organizations have shifted from having their own on-premises servers to using cloud hosting on Azure, AWS, or Google Cloud. Even the very nature of what a server is has changed with technologies, including containerization technologies such as Docker, platform as a service (PaaS) offerings such as Azure App Services, and serverless computing offerings such as Azure Functions and AWS Lambda.
Nowadays, newer AI technologies such as ChatGPT and GitHub Copilot Chat are poised to change what it even means to be a software developer, and this only underscores how much constant change is at the heart of the software engineering industry.
Change in software projects
In software development, change is a constant and can be unpredictable and sudden. All this change leads to code that was once considered perfect to later be considered a significant risk to the ongoing success of the business.
In other words, technical debt is to some degree or another an unavoidable part of software development. Thankfully, you can take some steps to reduce the rate at which it accumulates (as we’ll discuss in Part 2 of this book). Fortunately, we can detect technical debt through its symptoms, or “smells.”
So, how do you know whether your code has issues?
How do you know whether food has spoiled, clothing needs to be washed, or a diaper needs changing? It turns out that it just smells bad.
There are some metrics about what constitutes “good” and “bad” code, and we’ll explore them in Chapter 12: Code Analysis in Visual Studio and Chapter 16: Adopting Code Standards. Smelly code can be subjective to some degree or another. A developer who wrote a section of code or frequently modifies that portion of code may find the code to be more tolerable than a developer encountering the code for the first time.
While not all pieces of technical debt are identical, it turns out that many pieces of legacy code share a set of common symptoms.
These symptoms are commonly referred to as “code smells” and can include the following:
It’s difficult to understand what it does or why it does itYou or people on your team avoid working with itIt’s slower to modify than other areas or tends to break when modifiedIt’s hard to test or debugNew code starts out good and pristine, but real code that lives in a business setting evolves over time as more capabilities are required and additional features and fixes are introduced. As that happens, code that was once nice and neat starts to accumulate code smells.
Not all code is created equal, and not all code lasts as long as other pieces of code. Certainly, there are things we can do to make our code more resilient (as we’ll see in Chapter 8: Avoiding Code Anti-Patterns with SOLID). However, at some point in time, your nice and shiny new code will start to get smelly and will need to be cleaned up through a process called refactoring.
Refactoring is one of those words that doesn’t make a lot of sense to newer programmers, but here’s a simple definition:
Refactoring is the act of changing the shape or form of code without changing its functionality or behavior.
There are two key concepts here:
The first concept is that refactoring is an effort to improve the maintainability of existing code. Sometimes, restructuring means introducing a new variable, method, or class. Other times, refactoring simply changes how individual lines of code are arranged or which language features are used. Even something as simple as renaming a variable could be considered a small act of refactoring.The second concept in this definition is that refactoring does not alter the behavior of the code in question. Refactoring is a structural change done to bring some piece of technical merit without altering the existing behavior of your code. If a method typically returned a certain value before you refactored it and now it returns a different value, that is a change and not a refactoring.Refactoring also should provide some benefit to the engineering team. The code resulting from refactoring should be easier to understand, less likely to break when changed, and have less technical debt and fewer code smells than the starting code did.
Every line of code the development team produces should have a business value. Refactoring is no different, except the business value it produces should be more maintainable code with fewer issues and delays arising from its presence.
Sometimes, we try to improve our code through refactoring and we accidentally introduce new behavior – typically in the form of new bugs. This makes our refactoring become an unintentional change in the software that can result in emergency fixes to restore code to a working state.
Breaking code while refactoring can be a critical problem and a significant barrier to being allowed to perform refactored code in the future, which in turn can allow technical debt to thrive.
In Part 2 of this book, we’ll explore ways of safely refactoring your code so that you don’t accidentally introduce bugs, while in Part 4, we’ll discuss getting organizational buy-in to refactor your code, and what to do when a defect does arise out of your refactoring efforts.
Thankfully, all editions of Visual Studio now include refactoring tools built into the editor that allow you to quickly perform a set of common refactorings in a reliable and repeatable manner.
In Chapter 2: Introduction to Refactoring and the remaining chapters in Part 1, we’ll see a number of refactorings in action. Here’s a preview of some of the refactoring options Visual Studio provides the user:
Figure 1.1 – Visual Studio Quick Actions context menu showing a set of refactoring operations
Tool-assisted refactorings such as these are fantastic for a few reasons:
They are fast and efficientThey are reliable and repeatableThey rarelyintroduce defectsCaution
Note that I use the word rarely when talking about bugs introduced by refactoring tools. There are a few rare scenarios where using the built-in refactoring tools without thinking about their actions may introduce bugs into your application. We’ll talk specifically about those areas as we encounter them in the following chapters.
Over the rest of Part 1, we’ll explore using these tools to quickly and effectively refactor your C# applications and talk about the types of scenarios in which you might use each one of these.
With all that our tools can do, it is important to remember that these tools are just one way of refactoring code. Often, the most effective ways of removing code smells involve a combination of writing code yourself and using the built-in refactoring tools.
Refactoring’s key value is the long-term health of an organization, but many obstacles to refactoring can come from the organization itself. To help illustrate the practical aspects of refactoring in a real organization, each chapter will involve a case study from a fictitious organization. Some chapters will focus entirely on code from the case study while others, such as this chapter, will conclude with a dedicated case study section. These case study sections illustrate the concepts of the chapter applied to a fictitious organization.
Let’s meet our first case study section and see how technical debt and legacy code affect a typical company.
The rest of this book will follow code examples from an airline called Cloudy Skies Airlines, or Cloudy Skies for short. Through these examples, we should be able to see how technical debt and refactoring can apply to a “real” organization and its software.
Note
Cloudy Skies is a fictitious airline company created for this book for teaching purposes only. Any resemblance to any real company is purely coincidental. Additionally, I have never worked in aviation, so the code examples presented in the book are likely significantly different from actual software systems used in the industry.
Cloudy Skies is an airline that’s been around for the past 50 years and currently operates a little over 500 jets in its fleet, serving around 70 cities in its region.
Twenty years ago, the airline made a major move and started replacing its aging software systems with custom in-house applications built by its development team. Cloudy Skies chose to use .NET and C#. The initial systems performed well and resulted in increased developer productivity and high-performance software applications, so Cloudy Skies continued to migrate its applications to .NET.
As time went by, the airline and its systems grew. The engineering team at Cloudy Skies was once held in high esteem as the pride and joy of the organization and a key to its future.
However, management has been somewhat frustrated by its engineering team over the past few years. Some of its key complaints include the following:
Product managers are frustrated by large estimates for seemingly simple changes to existing systems, and a growing amount of time between software releases due to long implementation times and numerous bugs.The Quality Assurance department has been overwhelmed by a growing number of bugs present in the software, a tendency for the same things to break repeatedly, and bugs appearing in seemingly unrelated areas when changes occur in other parts of the application.For its part, the engineering team feels overwhelmed by the code it’s working with. Strategic initiatives have been pushed aside for years while the organization has the team focus on urgent changes or tight deadlines for new releases. As a result, nobody has had time to address the growing amount of technical debt the team is facing.
The Cloudy Skies codebase is constantly growing in complexity to account for each new feature or “special case” added to the system. This complexity in turn makes the application harder to test, understand, and modify, which has led to difficulties in onboarding new developers and some experienced developers leaving the organization.
Out of frustration after several severe delays and high-profile bugs, Cloudy Skies brings in a new engineering manager and empowers the team to make changes to ensure the airline can stay efficient and effective in the years to come.
This engineering manager determines that the primary cause of these problems is technical debt and that targeted refactoring of the most critical areas throughout the suite of applications could significantly reduce risk and improve the team’s effectiveness going forward.
To its credit, management agrees and allows the team to allocate resources to pay down technical debt and improve the maintainability of the code through refactoring.
Throughout the rest of this book, we’ll follow aspects of this fictitious team’s journey in paying down technical debt and paving the way to a better future through refactoring.
Legacy code is an unavoidable byproduct of the forces of time and constant change that are present in software development projects. This legacy code becomes a breeding ground for technical debt, which threatens our productivity as developers and the quality of our software.
While technical debt can arise due to a number of reasons, refactoring is the cure. Refactoring reworks existing code into a more maintainable and less risky form, reducing our technical debt and helping us control our legacy code.
The more you understand the causes and effects of technical debt in your code, the better you’ll find yourself equipped to explain technical debt to others in your organization, advocate for refactoring, and avoid things that cause your code to decline in effectiveness over time.
In the next chapter, we’ll explore refactoring in more depth by walking through a set of targeted changes to improve a piece of sample code from the Cloudy Skies Airlines codebase.
You can find more information about technical debt, legacy code, and refactoring at the following URLs:
Defining Technical Debt: https://killalldefects.com/2019/12/23/defining-technical-debt/Identify Technical Debt: https://learn.microsoft.com/en-us/training/modules/identify-technical-debt/The True Cost of Technical Debt: https://killalldefects.com/2019/11/09/the-true-cost-of-technical-debt/Code refactoring: https://en.wikipedia.org/wiki/Code_refactoring