Monday 24 July 2023

Tips to help you free up storage on your Android, Apple and Web development machines

If you're using your macOS machine for Android, Apple or Web application development and you're running low on storage, here are some tips to help you free up some gigabytes:

  1. Delete build directories by running find . -type d -name build -exec rm -rf {} \; in the parent directory which contains your development projects.
  2. Delete node_modules directories by running find . -type d -name node_modules -exec rm -rf {} \; in the parent folder which contains your development projects.
  3. Delete old, unused SDK Platforms and SDK Tools via the SDK Manager in Android Studio.
  4. Delete old, unneeded Emulators via the Device Manager in Android Studio.
  5. Delete old, unneeded Simulator Runtimes via the Platforms tab in Xcode settings.
  6. Delete unavailable Simulators by running xcrun simctl delete unavailable.
  7. Delete files and folders from ~/Library/Developer/Xcode/DerivedData.
  8. Delete files and folders from ~/.gradle/caches.

Thursday 24 November 2022

Book Review: Clean Architecture, by Robert C Martin

I thought this book did a good job of presenting a number of ideas about software architecture. The three standout discussions in the book for me were the following: (1) The proposal that each of the three major programming paradigms (i.e. structured programming, object-oriented programming and functional programming) removes capabilities from the programmer and imposes discipline rather than adding new capabilities. (2) The definition of high-level and low-level components based on how far or near the component is to the system's inputs and outputs. (3) The importance of writing high-level components (i.e. business rules) so that they can be tested independent of low-level concerns like the database and the user interface.

Yes, this book did have an emphasis on dependency inversion and yes, taken to its extreme, I can see how someone might go on a rampage and create an interface for each and every class in the system. However, I think that is more a fault of the reader than the book itself. Perhaps, in fairness, the book could have called out the dangers and drawbacks of taking dependency inversion to this kind of extreme. All in all, I thought this book could have done with a bit more editing and condensing but it was perfectly readable and there is plenty in it to think about and take away.

See below for some definitions and quotes from the book.


Programming paradigms: Ways of programming unrelated to any particular programming language. A paradigm tells you which programming structures to use and when to use them. 
Paradigms -> Structured Programming: Imposes discipline on direct transfer of control. Unrestrained jumps like goto statements are replaced with if/then/else and do/while/until constructs. 
Paradigms -> Object-Oriented Programming: Imposes discipline on indirect transfer of control. Promotes polymorphism through the disciplined use of function pointers. 
Paradigms -> Functional Programming: Imposes discipline upon assignment. A foundational notion is immutability. 
Dependency Inversion: The source code dependency between a low-level class L and the interface I that it implements points in the opposite direction to the flow of control from a high-level class H and the call that it makes to I. The source code dependencies are inverted against the flow of control. 
The Single Responsibility Principle: A module should be responsible to one and only one actor. Think of "actor" as the singular group of users or stakeholders who may provide a reason for the module to change. Think of "module" as a source file or a cohesive set of functions and data structures. 
Cohesion: The force that binds together the code responsible to a single actor.
Architectural Boundary: A line that separates software elements from one another and restricts those on one side from knowing about those on the other. Ideally, this line divides the system into two components: one abstract and the other concrete. The abstract component contains all the high-level business rules of the application. The concrete component contains all the implementation details that those business rules manipulate.
Dependency Rule: Dependencies cross the architectural boundary lines in one direction: toward more abstract entities containing the higher level policy. 
Components: The units of deployment. They are the smallest entities that can be deployed as part of a system. 
Reuse/Release Equivalence Principle: The granule of reuse is the granule of release. Another way to look at it: Classes and modules that are grouped together into a component should be releasable together. The fact that they share the same version number and the same release tracking, and are included under the same release documentation, should make sense both to the authors and to the users. 
Common Closure Principle: Gather into components those classes that change for the same reasons and at the same times. Separate into different components those classes that change at different times and for different reasons. This is the Single Responsibility Principle restated for components: A component should not have multiple reasons to change. Another way to look at it: If two classes are so tightly bound that they always change together, then they belong in the same component. 
Common Reuse Principle: Don't force users of a component to depend on things they don't need. Another way to look at it: When we depend on a component, we want to make sure that we depend on every class in that component. This principle tells us more about which classes shouldn't be together than about which classes should be together. 
The Acyclic Dependencies Principle: Allow no cycles in the component dependency graph. A cycle in the component dependency graph means that the components in the cycle have, in effect, become one large component. The developers working on any of these components will be stepping over one another because they must all use exactly the same release of one another's components. Also, to test any one component in this cycle, the developers must build and integrate with all of the components. 
Stability: How easy or difficult it is to change a software component. A component with lots of incoming dependencies is very stable because it requires a great deal of work to reconcile any changes with all the dependent components. 
The Stable Dependencies Principle: Depend in the direction of stability. Any component that we expect to be volatile should not be depended on by a component that is difficult to change. By conforming to this principle, we ensure that modules that are intended to be easy to change are not depended on by modules that are harder to change. 
The Stable Abstractions Principle: A component should be as abstract as it is stable. Taken together with the Stable Dependencies Principles, this implies that dependencies run in the direction of abstraction. 
Level (as in: high-level vs low-level): The distance from the inputs and outputs. The farther a policy is from both the inputs and outputs of the system, the higher its level. The policies that manage input and output are the lowest-level policies in the system. 
Entity: An object within our computer system that embodies a small set of critical business rules operating on critical business data. The object either contains the critical business data or has very easy access to that data. The interface of the object consists of functions that implement the critical business rules that operate on that data. 
Use Case: A description of the way that an automated system is used. It specifies the input to be provided by the user, the output to be returned to the user, and the processing steps involved in producing that output.


Foreword: "Not only does a good architecture meet the needs of its users, developers, and owners at a given point in time, but it also meets them over time."
Foreword: "The only way to go fast, is to go well."
Preface: "Why should such different systems all share similar rules of architecture? My conclusion is that the rules of software architecture are independent of every other variable."
Introduction: "Getting software right is *hard*. It takes knowledge and skills that most young programmers haven't yet acquired. It requires thought and insight that most programmers don't take the time to develop. It requires a level of discipline and dedication that most programmers never dreamed they'd need. Mostly, it takes a passion for the craft and the desire to be a professional."
Introduction: "... when you get software right, something magical happens: You don't need hordes of programmers to keep it working. You don't need massive requirements documents and huge issue tracking systems."
Introduction: "When software is done right, it requires a fraction of the human resources to create and maintain. Changes are simple and rapid. Defects are few and far between. Effort is minimised, and functionality and flexibility are maximised."
Introduction: "Have you worked on systems that are so interconnected and intricately coupled that every change, regardless of how trivial, takes weeks and involves huge risks?"
Introduction: "It is far more common to fight your way through terrible software designs than it is to enjoy the pleasure of working with a good one."
What is design and architecture / The Goal? "The goal of software architecture is to minimise the human resources required to build and maintain the required system."
What is design and architecture? / The Goal? "The measure of design quality is simply the measure of the effort required to meet the needs of the customer. If that effort is low, and stays low throughout the lifetime of the system, the design is good. If that effort grows with each new release, the design is bad."
What is design and architecture? / What Went Wrong? "The developers may think that the answer is to start over from scratch and redesign the whole system – but that's just the Hare talking again. The same overconfidence that led to the mess is now telling them that they can build it better if only they can start the race over."
A Tale of Two Values / Architecture: "Software was invented to be 'soft'. It was intended to be a way to easily change the behaviour of machines. If we'd wanted the behaviour of machines to be hard to change, we would have called it *hardware*."
A Tale of Two Values / The Greater Value: "... there's no such thing as a program that is impossible to change. However, there are systems that are practically impossible to change, because the cost of change exceeds the benefit of change."
A Tale of Two Values / The Greater Value: "... it is the responsibility of the software development team to assert the importance of architecture over the urgency of features."
Paradigm Overview / Food For Thought: "Each of the [programming] paradigms [i.e. structured programming, object-oriented programming and functional programming] *removes* capabilities from the programmer. None of them adds new capabilities. Each imposes some kind of extra discipline that is *negative* in its intent. The paradigms tell us what *not* to do, more than they tell us what *to* do."
Paradigm Overview / Food For Thought: "... each [programming] paradigm [i.e. structured programming, object-oriented programming and functional programming] takes something away from us."
Structured Programming / Proof: "... all programs can be constructed from just three structures: sequence, selection, and iteration."
Structured Programming / Science To The Rescue: "Science does not work by proving statements true, but rather by *proving statements false*. Those statements that we cannot prove false, after much effort, we deem to be true enough for our purposes."
Structured Programming / Science To The Rescue: "... mathematics is the discipline of proving provable statements true. Science, in contrast, is the discipline of proving provable statements false."
Structured Programming / Tests: "... a program can be proven incorrect by a test, but it cannot be proven correct. All the tests can do, after sufficient testing effort, is allow us to deem a program to be correct enough for our purposes."
Structured Programming / Tests: "Software development is not a mathematical endeavour, even though it seems to manipulate mathematical constructs. Rather, software is like a science. We show correctness by failing to prove incorrectness, despite our best efforts."
Functional Programming / Squares of Integers: "Variables in functional languages *do not vary*."
Functional Programming / Immutability and Architecture: "All race conditions, deadlock conditions, and concurrent update problems are due to mutable variables. You cannot have a race condition or a concurrent update problem if no variable is ever updated. You cannot have deadlocks without mutable locks."
The Single Responsibility Principle: "... separate the code that different actors depend on."
The Single Responsibility Principle: "... separate code that supports different actors."
Open-Closed Principle: "... the behaviour of a software artifact ought to be extensible, without having to modify that artifact."
Open-Closed Principle / A Thought Experiment: "If component A should be protected from changes in component B, then component B should depend on component A."
Open-Closed Principle / A Thought Experiment: "We want to protect the *Controllers* from changes in the *Presenters*. We want to protect the *Presenters* from changes in the *Views*."
Components: "... well-designed components always retain the ability to be independently deployable and, therefore, independently developable."
Component Cohesion / The Tension Diagram for Component Cohesion: "The three [component] cohesion principles tend to fight each other. The REP [Reuse/Release Equivalence Principle] and CCP [Common Closure Principle] are *inclusive* principles: Both tend to make components larger. The CRP [Common Reuse Principle] is an *exclusive* principle, driving components to be smaller. It is the tension between these principles that good architects seek to resolve."
Component Coupling / Top-Down Design: "... component dependency diagrams have very little to do with describing the function of the application. Instead they are a map to the *buildability* and *maintainability* of the application."
Component Coupling / The Stable Dependencies Principle: "One sure way to make a software component difficult to change, is to make lots of other software components depend on it."
What is Architecture? "The architecture of a software system is the shape given to that system by those who build it. The form of that shape is in the division of that system into components, the arrangement of those components, and the ways in which those components communicate with each other. The purpose of that shape is to facilitate the development, deployment, operation, and maintenance of the software system contained within it. The strategy behind that facilitation is to leave as many options open as possible, for as long as possible."
What is Architecture? "... the architecture of a system has very little bearing on whether that system works. There are many systems out there, with terrible architectures, that work just fine. Their troubles do not lie in their operation; rather, they occur in their deployment, maintenance, and ongoing development."
What is Architecture? "Good architecture makes the system easy to understand, easy to develop, easy to maintain, and easy to deploy. The ultimate goal is to minimise the lifetime cost of the system and to maximise programmer productivity."
What is Architecture? / Keeping Options Open: "... software has two types of value: the value of its behaviour and the value of its structure. The second of these is the greater of the two because it is this value that makes software *soft*."
What is Architecture? / Keeping Options Open: "The way you keep software soft is to leave as many options open as possible, for as long as possible. What are the options that we need to leave open? *They are the details that don't matter.*"
What is Architecture? / Keeping Options Open: "The goal of the architect is to create a shape for the system that recognises policy [i.e. the business rules and procedures] as the most essential element of the system while making the details [i.e. IO devices, databases, frameworks etc.] *irrelevant* to that policy."
What is Architecture? / Keeping Options Open: "If you have a portion of the high-level policy working, and it is agnostic about the database, you could try connecting it to several different databases to check applicability and performance... The longer you leave options open, the more experiments you can run, the more things you can try, and the more information you will have when you reach the point at which those decisions can no longer be deferred."
What is Architecture? / Keeping Options Open: "A good architect pretends that the decision has not been made, and shapes the system such that those decisions can still be deferred or changed for as long as possible."
What is Architecture? / Keeping Options Open: "A good architect maximises the number of decisions not made."
What is Architecture? / Conclusion: "Good architects carefully separate details from policy, and then decouple the policy from the details so thoroughly that the policy has no knowledge of the details and does not depend on the details in any way."
Independence / Use Cases: "The most important thing a good architecture can do to support behaviour is to clarify and expose that behaviour so that the intent of the system is visible at the architecture level... Developers will not have to hunt for behaviours, because those behaviours will be first-class elements visible at the top level of the system."
Independence / Decoupling Layers: "... separate those things that change for different reasons... collect those things that change for the same reasons..."
Boundaries: Drawing Lines: "What kinds of decisions are premature? Decisions that have nothing to do with the business requirements – the use cases – of the system. These include decisions about frameworks, databases, web servers, utility libraries, dependency injection, and the like. A good system architecture is one in which decisions like these are rendered ancillary and deferrable. A good system architecture does not depend on these decisions. A good system architecture allows those decisions to be made at the latest possible moment, without significant impact."
Boundaries: Drawing Lines / Which Lines Do You Draw, And When Do You Draw Them: "The database is a tool that the business rules can use *indirectly*. The business rules don't need to know about the schema, or the query language, or any other details about the database. All the business rules need to know is that there is a set of functions that can be used to fetch or save data."
Boundary Anatomy / The Dreaded Monolith: "High-level components remain independent of lower-level details."
Boundary Anatomy / Local Processes: "... the architectural goal is for lower-level processes to be plugins to higher-level processes."
Boundary Anatomy / Services: "Lower-level services should *plug in* to higher-level services."
Policy And Level: "... lower-level components should be plugins to the higher-level components."
Business Rules: "How the data gets in and out of the system is irrelevant to the use cases."
Screaming Architecture / The Purpose of an Architecture: "Good architectures are centred on use cases so that architects can safely describe the structures that support those use cases without committing to frameworks, tools, and environments."
Screaming Architecture / The Purpose of an Architecture: "... consider the plans for a house. The first concern of the architect is to make sure that the house is usable – not to ensure that the house is made of bricks. Indeed, the architect takes pains to ensure that the homeowner can make decisions about the exterior material (bricks, stone, or cedar) later..."
Screaming Architecture / Frameworks Are Tools, Not Ways of Life: "Look at each framework with a jaded eye. View it skeptically. Yes, it might help, but at what cost? Ask yourself how you should use it, and how you should protect yourself from it. Think about how you can preserve the use-case emphasis of your architecture. Develop a strategy that prevents the framework from taking over that architecture."
Screaming Architecture / Testable Architectures: "If your system architecture is all about the use cases, and if you have kept your frameworks at arm's lengths, then you should be able to unit-test all those use cases without any of the frameworks in place. You shouldn't need the web server running to run your tests. You shouldn't need the database connected to run your tests. Your Entity objects should be plain old objects that have no dependencies on frameworks or databases or other complications. Your use case objects should coordinate your Entity objects."
Screaming Architecture / Conclusion: "Your architecture should tell readers about the system, not about the frameworks you used in your system. If you are building a health care system, then when new programmers look at the source repository, their first impression should be, 'Oh, this is a health care system.' Those new programmers should be able to learn all the use cases of the system, yet still not know how the system is delivered."
The Test Boundary / Design for Testability: "... design the system, and the tests, so that business rules can be tested without using the GUI."
Clean Embedded Architecture: "Although software does not wear out, it can be destroyed from within by unmanaged dependencies on firmware and hardware."
Clean Embedded Architecture: "Being able to test only in the target hardware is not good for your product's long-term health."

Monday 31 October 2022

A review of the Google Play app review process

The glory days of uploading a build of your app to the Google Play store and having it in the hands of your users within hours is well behind us.

The Play Console Help documentation says to expect "review times of up to seven days or longer in exceptional cases." In practice, the exceptional cases are not so exceptional. Take this reddit thread and the stories within it as an example.

In my case, the review process this past month took a whopping 38 days. I waited 24 days for the upload of my app to the Open Testing track to be approved and I then waited 14 more days for the promotion of this build to the Production track to be approved. 38 days waiting for a minor update to make its way into the hands of my users. Take that in. The process is broken.

Friday 23 September 2022

Android runtime permission APIs: A critique

How frustrating is it that the Android runtime permission APIs do not allow us to ask a very simple question of the operating system: "Has the user denied my app permission X permanently?"

This question is important because the operating system will ignore the app's requests for permission X via requestPermissions(permissions:requestCode:) if the user has denied the permission twice already (see here if this is news to you).

If we could ask the above question of the operating system, we would know when to call requestPermissions(permissions:requestCode:) and when to direct the user to the Settings app instead.

For a concrete but minimal app that demonstrates this problem, see here. Likewise, for an app that demonstrates a workaround, see here.

Friday 15 April 2022

RecyclerView: Add dividers and spaces between items

It turns out that adding a divider or a space between items in a RecyclerView is non-trivial. You have to extend the RecyclerView.ItemDecoration abstract class and then get into the weeds of the RecyclerView class. It should be simple but this is the state of Android development. I'm sure you'd rather skip over the details and make a simple call as follows:

myRecyclerView.setColoredDivider(dividerColor, dividerHeightPixels)
// or

If so, then add the com.tazkiyatech:android-utils library to your project and benefit from the Kotlin extension functions defined in the RecyclerViewExtensions.kt file. If you like complexity, then I'll point you to the and files instead.

Friday 24 December 2021

Gradle: Commands to make sense of your project's dependencies

In the code examples below, I assume you are working in a multi-project build and one of the subprojects in this build is named "app". If you are working in a single-project build, then the commands you want to run are of the form gradle someTask and not gradle :app:someTask.
The first and simplest command you can run to print a dependency graph for each and every configuration in your project is:

gradle :app:dependencies

If you want to print a dependency graph for only one of the configurations in your project, then the command to run is:

gradle :app:dependencies --configuration someConfiguration

To print a list of all of the configurations in your project, the command to run is:

gradle :app:dependencies | grep ' - '

Lastly, if all you want is to get insight into a single dependency within a single configuration in your project, then the comamnd to run is:

./gradlew :app:dependencyInsight --configuration someConfiguration --dependency someGroup:someName

Saturday 20 November 2021

Book Review: Working Effectively With Legacy Code, by Michael Feathers

For me, this book lived up to the hype. Essentially, it defines legacy code as any code that does not have supporting tests and it provides lots of examples of how to get such code under test. I suppose, over time, I will forget the particular examples and techniques described in the book for getting code under test. However, I'm certain the principles will stick with me: Get the system under test before taking any steps to get it "right".

Below are some definitions:

Cover and Modify: Working with a safety net when we change software. The safety net isn't something that we put underneath our tables to catch us if we fall out of our chairs. Instead, it's like a cloak that we put over code we are working on to make sure that bad changes don't leak out and infect the rest of our software. Covering software means covering it with tests. This contrasts with the Edit and Pray approach to changing software.

The Legacy Code Dilemma: "When we change code, we should have tests in place. To put tests in place, we often have to change code."

Seam: "A seam is a place where you can alter behaviour in your program without editing in that place."

Enabling Point: "Every seam has an enabling point, a place where you can make the decision to use one behaviour or another."

Refactoring: "A change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its existing behaviour. "

Command/Query Separation: "A method should be a command or a query, but not both. A command is a method that can modify the state of the object but that doesn't return a value. A query is a method that returns a value but that does not modify the object. Why is this principle important? There are a number of reasons, but the most primary is communication. If a method is a query, we shouldn't have to look at its body to discover whether we can use it several times in a row without causing some side effect."

Effects and Encapsulation: "Encapsulation is important, but the reason why it is important is more important. Encapsulation helps us reason about our code. In well-encapsulated code, there are fewer paths to follow as you try to understand it. Breaking encapsulation can make reasoning about our code harder, but it can it easier if we end up with good explanatory tests afterward. When we have test cases for a class, we can use them to reason about our code more directly. We can also write new tests for any questions that we might have about the behaviour of the code." 

Interception point: "A point in your program where you can detect the effects of a particular change. In some applications, finding them is tougher than it is in others. If you have an application whose pieces are glued together without many natural seams, finding a decent interception point can be a big deal. Often it requires some effect reasoning and a lot of dependency breaking." 

Characterisation test: "A test that characterises the actual behaviour of a piece of code. There's no "Well, it should do this" or "I think it does that." The tests document the actual current behaviour of the system. If we find something unexpected when we write them, it pays to get some clarification. It could be a bug. That doesn't mean that we don't include the test in our test suite; instead, we should mark it as suspicious and find out what the effect would be of fixing it." 

Scratch refactoring: A technique for learning about code which runs as follows: check out the code from your version-control system, forgot about writing tests, extract methods, move variables, refactor it whatever way you want to get a better understanding of it, and then throw that code away. It's a great way of getting down to the essentials and really learning how a piece of code works.

Single-Responsibility Principle: Every class should have a single responsibility: It should have a single purpose in the system, and there should be only one reason to change it. 

Interface Segregation Principle: "When a class is large, rarely do all of its clients use all of its methods. Often we can see different groupings of methods that particular clients use. If we create an interface for each of these groupings and have the large class implement those interfaces, each client can see the big class through that particular interface. This helps us hide information and also decreases dependency in the system. The clients no longer have to recompile whenever the large class does." 

Open/Closed Principle: "... when we have good design, we just don't have to change code much to add new features." 

Safety first: "Once you have tests in place, you can make invasive changes much more confidently."

Below are some heuristics for seeing responsibilities in existing code:

  1. Group methods: Look for similar method names. Write down all of the methods on a class, along with their access types (public, private, and so on), and try to find ones that seem to go together.
  2. Look at hidden methods: Pay attention to private and protected methods. If a class has many of them, if often indicates that there is another class in the class dying to get out.
  3. Look for decisions that can change: Look for decisions – not decisions that you are making in the code, but decisions that you've already made. Is there some way of doing something (talking to a database, talking to another set of objects, and so) that seems hard-coded? Can you imagine it changing?
  4. Look for internal relationships: Look for relationships between instance variables and methods. Are certain instance variables used by some methods and not others?
  5. Look for the primary responsibility: Try to describe the responsibility of the class in a single sentence.

Below are some quotes:

Foreword: "It's not enough to prevent the rot – you have to be able to reverse it."

Foreword: "... turn systems that gradually degrade into systems that gradually improve."

Preface: "Code can degrade in many ways, and many of them have nothing to do with whether the code came from another team."

Preface: "... legacy code is simply code without tests."

Preface: "Code without tests is bad code. It doesn't matter how well written it is; it doesn't matter how pretty or object-oriented or well-encapsulated it is. With tests, we can change the behaviour of our code quickly and verifiably. Without them, we don't know if our code is getting better or worse."

Preface: "Teams take serious chances when they try to make large changes without tests. It is like doing aerial gymnastics without a net. It requires incredible skill and a clear understanding of what can happen at every step."

Preface: "... don't be surprised if some of the steps you take to make changes involve making some code slightly uglier. This work is like surgery. We have to make incisions, and we have to move through the guts and suspend some aesthetic judgement."

Changing Software: "The difference between good systems and bad ones is that, in the good ones, you feel pretty calm after you've done that learning, and you are confident in the change you are about to make. In poorly structured code, the move from figuring things out to making changes feels like jumping off a cliff to avoid a tiger."

Working with feedback: "... safety isn't solely a function of care. I don't think any of us would choose a surgeon who operated with a butter knife just because he worked with care. Effective software change, like effective surgery, really involves deeper skills."

Working with feedback: "When we have a good set of tests around a piece of code, we can make changes and find out very quickly whether the effects were good or bad."

Working with feedback: "When we have tests that detect changes, it is like having a vise around our code. The behaviour of the code is fixed in place. When we make changes, we can know that we are changing only one piece of behaviour at a time. In short, we're in control of our work."

Working with feedback: "Unit testing is one of the most important components in legacy code work. System-level regression tests are great, but small, localised tests are invaluable. They can give you feedback as you develop and allow you to refactor with much more safety."

Working with feedback: "... in unit testing, we are usually concerned with the most atomic behavioural units of a system. In procedural code, the unit tests are often functions. In object-oriented code, the units are classes."

Working with feedback: "... qualities of good unit tests: (1) They run fast. (2) They help us localise problems."

Working with feedback: "A test is not a unit test if: (1) It talks to a database. (2) It communicates across a network. (3) It touches the file system. (4) You have to do special things to your environment to run it."

Working with feedback: "... when we cover our code with tests before we change it, we're more likely to catch any mistakes that we make."

Working with feedback: "Dependency is one of the most critical problems in software development. Much legacy code work involves breaking dependencies so that change can be easier."

Working with feedback: "When you break dependencies in legacy code, you often have to suspend your sense of aesthetics a bit. Some dependencies break cleanly; others end up looking less than ideal from a design point of view. They are like incision points in surgery: There might be a scar left in your code after your work, but everything beneath it can get better."

Working with feedback: "We want to make functional changes that deliver value while bringing more of the system under test. At the end of each programming episode, we should be able to point not only to code that provides some new feature, but also its tests."

Sensing and separation: "When we write tests for individual units, we end up with small, well-understood pieces."

The Seam Model: "Pulling classes out of existing projects for testing really changes your idea of what "good" is with regard to design." 

It takes forever to make a change: "Systems that are broken up into small, well-named, understandable pieces enable faster work." 

How do I add a feature?: "The most powerful feature-addition technique I know of is test-driven development (TDD)... We imagine a method that will help us solve some part of a problem, and then we write a failing test case for it. The method doesn't exist yet, but if we can write a test for it, we've solidified our understanding of what the code we are about to write should do."

I can't run this method in a test harness: "Good design is testable, and design that isn't testable is bad."

I can't run this method in a test harness: "... the pain that we feel working in a legacy code base can be an incredible impetus to change. We can take the sneaky way out [i.e. hack around problems], but unless we deal with the root causes, overly responsible classes and tangled dependencies, we are just delaying the bill. When everyone discovers just how bad the code has gotten, the costs to make it better will have gotten too ridiculous."

Effects and Encapsulation: "Encapsulation and test coverage isn't always at odds, but when they are, I bias toward test coverage. Often it can help me get more encapsulation later."

Effects and Encapsulation: "Encapsulation isn't an end in itself; it is a tool for understanding."

I need to make many changes in one area: "The discussions that you have about naming have benefits far beyond the work that you are currently doing. They help you and your team develop a common view of what the system is and what it can become."

I need to make a change: "... finding bugs in legacy code usually isn't a problem. In terms of strategy, it can actually be misdirected effort. It is usually better to do something that helps your team start to write correct code consistently. The way to win is to concentrate effort on not putting bugs into code in the first place."

Characterisation tests: "In nearly every legacy system, what the system does is more important than what it is supposed to do. If we write tests based on our assumption of what the system is supposed to do, we're back to bug finding again. Bug finding is important, but our goal right now is to get tests in place that help us make changes more deterministically."

Characterisation tests: "We aren't trying to find bugs [when writing tests for legacy code]. We are trying to put in a mechanism to find bugs later, bugs that show up as differences from the system's current behaviour. When we adopt this perspective, our view of our tests is different: They don't have any moral authority; they just sit there documenting what pieces of the system really do."

Dependencies on libraries are killing me: "Avoid littering direct calls to library classes in your code. You might think that you'll never change them, but that can become a self-fulfilling prophecy."

My application has no structure: "When teams aren't aware of their architecture, it tends to degrade." 

My application has no structure: "... architecture is too important to be left exclusively to a few people. It's fine to have an architect, but the key way to keep an architecture intact is to make sure that everyone on the team knows what it is and has a stake in it. Every person who is touching the code should know the architecture..."

My application has no structure: "If you have, say, a team of 20 people and only 3 people know the architecture in detail, either those 3 have to do a lot to keep the other 17 people on track or the other 17 people just make mistakes caused by unfamiliarity with the big picture."

Telling the Story of the System: "... explain the architecture of the system using only a few concepts, maybe as few as two or three... Pragmatic considerations often keep things from getting simple, but there is value in articulating the simple view. At the very least, it helps everyone understand what would've been ideal and what things are there as expediencies. The other important things about this technique is that it really forces you to think about what is important in the system." 

Telling the Story of the System: "Teams can only go so far when the system they work on is a mystery to them. In an odd way, having a simple story of how a system works just serves as a roadmap, a way of getting your bearing as you search for the right places to add features. It can also make a system a lot less scary."

Telling the Story of the System: "On your team, tell the story of the system often, just so that you share a view. Tell it in different ways. Trade off whether one concept is more important than another. As you consider changes to the system, you'll notice that some changes fall more in line with the story. That is, they make the briefer story feel like less of a lie. If you have to choose between two ways of doing something, the story can be a good way to see which one will lead to an easier-to-understand system."

Telling the Story of the System: "When we simplify and rip away details to describe a system, we are really abstracting. Often when we force ourselves to communicate a very simple view of a system, we can find new abstractions."

Telling the Story of the System: "If a system isn't as simple as the simplest story we can tell about it, does that mean that it's bad? No. Invariably, as systems grow, they get more complicated. The story gives us guidance."

My application has no structure: "... there is something mesmerising about large chunks of procedural code: They seem to beg for more."

Adding New Behaviour: "Often the work of trying to formulate a test for each piece of code that we're thinking of writing leads us to alter its design in good ways. We concentrate on writing functions that do some piece of computational work and then integrate them into the rest of the application."

This class is too big: "Many of the features that people add to systems are little tweaks. They require the addition of a little code and maybe a few more methods. It's temping to just make these changes to an existing class. Chances are, the code that you need to add must use data from some existing class, and the easiest thing is to just add code to it. Unfortunately, this easy way of making changes can lead to some serious trouble. When we keep adding code to existing classes, we end up with long methods and large classes. Our software turns into a swamp, and it takes more time to understand how to add new features or even just understand how old features work."

This class is too big: "What are the problems with big classes? The first is confusion. When you have 50 or 60 methods on a class, it's often hard to get a sense of what you have to change and whether it is going to affect anything else. In the worst case, big classes have an incredible number of instance variables, and it is hard to know what effects are of changing a variable. Another problem is task scheduling. When a class has 20 or so responsibilities, chances are, you'll have an incredible number of reasons to change it. In the same iteration, you might have several programmers who have to do different things to the class. If they are working concurrently, this can lead to some serious thrashing, particularly because of the third problem: Big classes are a pain to test."

This class is too big: "Classes that are too big often hide too much... when we encapsulate too much, the stuff inside rots and festers. There isn't any easy way to sense the effects of change, so people fall back on Edit and Pray programming. At that point, either changes take far too long or the bug count increases. You have to pay for the lack of clarity somehow."

This class is too big: "When you put new code into a new class, sure, you might have to delegate from the original class, but at least you aren't making it much bigger."

This class is too big: "If you add code in a new method, yes, you will have an additional method, but at the very least, you are identifying and naming another thing that the class does; often the names of methods can give you hints about how to break down a class into smaller pieces."

Seeing responsibilities: "Learning to see responsibilities is a key design skill, and it takes practice. It might seem off to talk about a design skill in this context of working with legacy code, but there really is little difference between discovering responsibilities in existing code and formulating them for code that you haven't written yet. The key thing is to be able to see responsibilities and learn how to separate them well. If anything, legacy code offers far more possibilities for the application of design skill than new features do. It is easier to talk about design tradeoffs when you see the code that will be affected, and it also easier to see whether structure is appropriate in a given context because the context is real and right in front of us."

Seeing responsibilities: "... we are not inventing responsibilities; we're just discovering what is there. Regardless of what structure legacy code has, its pieces do identifiable things."

Seeing responsibilities: "The more you start noticing the responsibilities inherent in code, the more you learn about it."

Seeing responsibilities: "If you can identify some of these responsibilities that are a bit off to the side of the main responsibility of the class, you have a direction in which you can take the code over time."

Seeing responsibilities: "... if you have the urge to test a private method, the method shouldn't be private; if making the method public bothers you, chances are, it is because it is part of a separate responsibility. It should be on another class." 

Single-goal editing: "I have this little mantra that I repeat to myself when I'm working: "Programming is the art of doing one thing at a time." When I'm pairing, I always ask my partner to challenge me on that, to ask me "What are you doing?" If I answer more than one thing, we pick one. I do the same for my partner. Frankly, it's just faster. When you are programming, it is pretty easy to pick off too big of a chunk at a time. If you do, you end up thrashing and just trying things out to make things work rather than working very deliberately and really knowing what your code does." 

Pair Programming: "... working in legacy code is surgery, and doctors never operate alone." 

We feel overwhelmed: "... I've visited teams with millions of lines of legacy code who looked at each day as a challenge and as a chance to make things better and have fun... The attitude we bring to the work is important." 

Dependency-breaking techniques: "Code is harder to understand when it is littered with wide interfaces containing dozens of unused methods. When you create narrow abstractions targeted toward what you need, your code communicates better and you are left with a better seam." 

Dependency-breaking techniques: "Your bias should be toward making changes that you feel more confident in rather than changes that give you the best structure. Those can come after your tests." 

Dependency-breaking techniques: "... when we don't have tests in place and we are trying to do the minimal work we need to get tests in place, it is best to leave logic alone as much as possible." 

Dependency-breaking techniques: "Naming is a key part of design. If you choose good names, you reinforce understanding in a system and make it easier to work with. If you choose poor names, you undermine understanding and make life hellish for the programmers who follow you." 

Dependency-breaking techniques: "Although singletons do prevent people from making more than one instance of a class in production code, they also prevent people from making more than one instance of a class in a test harness."