This learning path introduces the basics of what is required for Developers to write quality code.

In all complex human endeavors there is value in capturing those aspects of our efforts which have been proven to be universally true. In medicine, for example, it was not initially considered particularly important to wash one’s hands and instruments; once this was proven and accepted by the overall community of doctors, the entire field became fundamentally more successful.

Professions are, among other things, entities that can capture these universal truths to pass them on to future generations and to ensure that everyone participating in the overall effort understands and adheres to them (The surgical operation does not go well if only some of the doctors and nurses involved believe in germs). We are ever-increasingly vulnerable to the quality of the work of other developers and development teams.

Whether you prefer to think of what you do as a profession, a craft, an art, a science, or some other term, there is value in knowing what these universals are and also what basic skills every developer should have. This can help to improve your value to your organization, certainly, but also can increase your own sense of confidence and self-worth.

Also, many of the aspects we list below are synergistic. That is, learning about one of them will help you to understand others, or to deepen your understanding of them all.

Of course, we cannot claim to have found everything that belongs on this list. This is what we know now, and will serve as a good starting point in trying to create this kind of shared universal knowledge. As we learn more, we will add to it.

Coupling

Coupling

Coupling refers “to the strength of a connection between two routines or classes. Coupling is a complement to cohesion. Cohesion describes how strongly the internal contents of a routine or class are related to each other. Coupling describes how strongly a routine is related to other routines. The goal is to create routines and classes with internal integrity (strong cohesion) and small, direct, visible, and flexible relations to other routines and classes (loose coupling).”

Benefits

Every time you create a connection between two points in a software system, you incur the risk that a change to one of those things will affect the other. Rampant, uncontrolled coupling drives the risk upward until it reaches the point of being a relative certainty that every change will have side-effects to which a developer must attend. At some point, the question transforms from “Will there be an unexpected side effect to this change?” to “How far reaching will this change need to be?” and the “ripple effect” is born.

Controlling coupling helps you limit that ripple effect.

Obstacles

We cannot simply eradicate all coupling from a software system, though, because coupling is required to make a software system work. If nothing can talk to anything else, then nothing will ever get done.

You also can’t treat coupling as a quantity, with “a lot” being bad and “a little” being good. If a software system has a lot of coupling but it is all required in order to solve a necessary problem, then there isn’t a coupling problem. If a software system has a little coupling but virtually none of it is required, then there is a coupling problem.

Instead of mere amount or ambiguous “tightness” and “looseness” pseudo-quantities, you need something that embraces the inherently qualitative nature of coupling. You need a way to measure and indicate how much of the coupling in a system is illogical or unnecessary.

Solutions

At Net Objectives, we feel the best approximation for necessity is intent. If all the coupling in a system is intentional, created for a logical purpose by a developer, then odds are most of it is necessary. If most of the coupling is accidental, something that simply slipped by without notice, then it is very likely that a system has a lot of unnecessary coupling in it. Also, if coupling was not intentionally created, then it will likely be forgotten about and the possibility of unintended side-effects becomes much more likely.

But it is not enough to identify whether your coupling is intentional, it is also important to know what kind of coupling it is. There are four kinds of coupling that we primarily concern ourselves with:

Identity: Coupling to Type; one type requires that another type must exist, or the first type will not compile. A good examine is a type-safe container of some collection of another type. The container would have identity coupling to the contained type, but not vice-versa.
Representational: Coupling to Interface; one type is coupled to the interface of another type. A good example is the client-service relationship. A client class is vulnerable to changes in the interface of the service class.
Inheritance: Coupling to a base class, or super-type. Changes to base classes can propagate downward to derived classes. This can be a good thing, but only if that was the intent.
Subclass: Coupling to the polymorphic nature of a service. When a client holds a reference to a type, but in fact it is a sub-type (or an implementing type if the reference is an interface) the question is, does the client know this? If so, then it has subclass coupling. If not, then it does not.
These four types of coupling are often necessary in most modern designs using most modern languages.

A fifth type of coupling is almost never required and it’s pretty safe to assume is always accidental (at least, it should always be accidental). That kind of coupling is implementation coupling: coupling to how something is done or its internal structure.

What is “good code”? It depends on what you want from it. In the beginning of the software development profession, computers were extremely slow and expensive, and developer time was considered to be a less-critical commodity. Not surprisingly, we did whatever we could to make things easy for the computer to do, even if it made the system very hard for the human developer to work with.

This dynamic has fundamentally changed. Computers are fast, cheap, and software is therefore pretty much everywhere. Developer time is the primarily expense of most development organizations. “Good Code” today, generally, is code that requires less developer time to change, reduces the risk of change, and which will help the system to maintain its ROI for as long as possible.

These are qualities of code that make for more flexible, maintainable and extensible code:

  • Coupling – the connections between units should make sense and be manageable
  • Cohesion – all the parts of a unit should be very strongly related to one another and, therefore, a unit should have a very limited scope of purpose
  • Encapsulation – a unit’s visibility into the details of other units should be as limited as possible to prevent, among other things, accidental coupling
  • Focus – it should be easy to find everything having to do with a single purpose
  • Readability – code should be easy for a professional, up-to-date software developer to read and understand
  • Avoiding Redundancy – anything that needs to be changed should be able to be changed in a single place
  • Testability – how testable something is reflects on the quality of its design and implementation

Knowing the qualities that define the nature of the code that you seek is useful, but we also need to know the underlying principles that will, most reliably, lead us to those qualities. Also, principles will help to keep us aligned with qualities when unusual, unforeseen, and unfamiliar problems arise. Principles are general guidance about creating good software; there is no “end” to following a principle, you could always “do more”… but in following them generally we find that everything gets incrementally easier to change, scale, and extend.

Here are principles that should be followed when creating software:

A practice is something we, as a community, have discovered is simply always a good thing to do. For something to be promoted as a practice, it must be truly universal (you can always do it, and everyone should do it). Therefore, it must be:

  1. Easy to do. We don’t want to burden ourselves or slow ourselves down because of a practice.
  2. Easy to teach. If we want everyone to do them, it should not be a burden to propagate their use across the entire team, organization, and the profession itself.
  3. Be highly valuable. A good practice should pay you back over and over and over again.

Returning to the medical analogy, all doctors wash their hands before dealing with their patients. It is not hard to do, it’s easy to teach others to do, and it has made the entire profession fundamentally more successful.

These are practices that meet all three of these standards.

Programming by Intention

The eXtreme Programming movement brought with it a set of practices that can help to keep code quality high without over-design or wasteful implementation. Similar to the concept of “top down programming” that was popular among Smalltalk and Cobol developers in the past, programming by intention essentially consists of creating methods by calling them, in consuming code, before they actually exist, and then implementing them afterward.

This simple procedure produces a surprisingly large number of positive results including:

  • Method cohesion
  • Good interface design
  • Ease in subsequent refactoring
  • More granular tests

…to name a few

Make State Private

State variables should be made private by default. If there is the need to make an object’s state available to an outside entity, then accessor/mutator methods (in languages like Java) or properties (.Net) should be provided.

This prevents other entities from become coupled to the nature of the state (how it is stored, that it is stored at all, where it is stored, etc…).

Similarly, state that is intended to be accessed by a derived class should be kept private in the base class, but protected accessor/mutator methods or properties should be provided. This keeps derived classes from coupling to the nature of the state it the base class.

Encapsulate Constructors

State variables should be made private by default. If there is the need to make an object’s state available to an outside entity, then accessor/mutator methods (in languages like Java) or properties (.Net) should be provided.

This prevents other entities from become coupled to the nature of the state (how it is stored, that it is stored at all, where it is stored, etc…).

Similarly, state that is intended to be accessed by a derived class should be kept private in the base class, but protected accessor/mutator methods or properties should be provided. This keeps derived classes from coupling to the nature of the state it the base class.

Perform Commonality-Variability Analysis

In his seminal work “Multi-Paradigm Design”, James Coplien suggested a technique for analyzing domains that tends to produce strong, useful abstractions that create architectural longevity and increase ROI. We feel this is such a valuable effort that we consider it a practice: Commonality-Variability Analysis.

It uses our natural ability to conceptualize complex elements in our environment to create opportunities for open-closed-ness in design.

Our profession is a young one, especially compared to others like the law, medicine, and so forth. However, we have enough history now to be able to draw upon the experience and wisdom of our senior members, those “grey hairs” who have encountered and analyzed many of the issues that confront us. Where we can, we should draw upon this wisdom, and then build upon it for those who follow us.

These are the elements of wisdom we believe every developer should understand.

From the Gang of Four

Design to Interfaces

This can be taken two ways:

  • First, when considering the signature of a method (its name, parameter list, and return type), make this decision based upon the need of the entity(ies) that will consume it, not from its implementation details.
  • Second, when creating relationships between entities (typically classes), try to do this between abstract types, not concrete types, whenever possible.

Favor Aggregation Over Inheritance

Object-Oriented languages typically support inheritance. Initially, our focus was on creating re-use by specializing types through deriving them into new types which took most of the existing behavior as-in, and then replaced one or more functions. While this can be beneficial, the GOF suggests that inheritance is a more powerful tool when used to create categories of implementing objects that can be made interchangeable. They refer to this as “aggregation” because the object that would previously have been specialized now delegates to this interchangeable object, and thus we have an aggregation of two entities now, instead of the specialization of one.

Encapsulate Variation

Encapsulating variation means simply this: when something varies in a system, it should appear to all the clients that consume the varying thing that it is not, in fact varying. For example, if there are multiple ways to calculate sales tax, it should appear to any calling code that there is a single sales tax algorithm. This simplifies client code, promotes cohesion, creates open-closed-ness, and makes system more testable.

From Martin Fowler

Separate Perspectives of Concept, Specification, and Implementation

In his book UML Distilled: A Brief Guide to the Standard Object Modeling Language (3rd Edition), Martin Fowler suggested that there are three different perspectives that Object-Oriented designs have within them:

The conceptual perspective: This has to do with the major abstractions that model a domain. In OO, we typically represent these conceptual issues using abstract classes, interfaces, and the like. However, other language elements can also be used to abstract; regular expressions are a good example of this. The conceptual perspective is concerned with what something “is.”

The specification perspective: This has to do with the ways things interact in a system, primarily regarding the parameters and returns of methods, and the collection of public methods that represent the interface of a class. The specification perspective is concerned with how something is “used.”

The Implementation perspective: This has to do with the actual implementing algorithmic code that creates the behavior in question. This can mean the code within a method, of course, but also the private methods of an object, which represent the way the problem being solved has been decomposed. The implementation perspective is concerned with what something “does.”

Good designs tend to keep these perspective separate. Base types in a polymorphic set of objects are conceptual only. Implementing subclasses are implementations only (they are not further specialized). Client objects couple only to specifications, not implementations. are, among other things, good examples of keeping these three perspectives separate under various circumstances.

From James Coplien

Use Abstractions to Create Longevity in Your Designs and Architecture

In his thesis “Multi-Paradigm Design,” James Coplien suggested that the major value of abstractions in design was to create the opportunity to add new behaviors without extensive re-design and waste. This is a practical application of the Open-Closed principle, in that wherever we couple to an abstraction rather than an im-plementation, we are open to new implementations without changing the client entities. If we do this extensively, we create longevity in our architectures. The practice of performing Commonality-Variability Analysis is a practical application of this principle, as it leads us to focus on abstractions early in the development process.

From Christopher Alexander

Favor Design by Differentiation Over Design by Synthesis

In “The Timeless Way of Building,” Christopher Alexander suggested that there are two distinct approaches to the overall activity of design, namely:

  1. Design by synthesis, in which we start by deriving the entities in the problem and then combine them into structures, and
  2. Design by differentiation, where we start with the larger concepts in the problem, find the patterns, and then implement the entities that are suggested by them.

While both approaches can create working designs, the latter tends to produce greater clarity, flexibility, and maintainability without adding complexity. Pattern-Oriented designs are achieved by a differentiated view, and are the main thesis of our course Design Patterns Explained.

Wisdom applies generally, as principles do, but also in specifics ways when confronted by specific kinds of problems. When these specifics repeat, there is value in sharing the patterns that we discover, giving them names to make them more shareable, and thereby creating a professional vocabulary.

In our profession there are many books on patterns. Trying to “learn all the patterns” is probably too much to ask, and also is not really necessary once we understand the principles, practices, and wisdom that underlie all patterns.

This list, taken overall, constitutes a Discipline, a shared set of distinctions that drive decisions and actions overall. At Net Objectives we call this discipline “Pattern-Oriented Development” because we use patterns to drive the overall process, not just to solve individual problems in implementation.

This is only one discipline, however. There is another, which we also believe all developers should know of, understand, and practice. This is the discipline of “Test-Driven Development.” The underlying qualities and principles are the same, but TDD “gets you there” in a different way. Also, there are specific patterns and practices in TDD that enable the process (refactoring, automated testing, mock objects, dependency injection, etc…)

Start typing and press Enter to search