In Kent Beck's book, Implementation Patterns, Beck states that there is no list of patterns that can cover every situation in programming. He claims that each pattern contains a bit of theory and that there are more pervasive forces at work in programming than are covered in individual patterns. These concerns have been divided into two types: values and principles.
Values are the overarching theme and should dictate every decision ever made in programming. There exists three values: communication, simplicity, and flexibility.
Communication is possibly the most important value in programming. As mentioned in a previous post, we have the responsibility for communicating well with potential readers. If good code is clean code, then clean code should read well and be easy to understand. Because developers work in teams, it is crucial to think of others as we code. Think to yourself, "How would someone else see this?" when writing code. This gives yourself a fresh perspective and you'll feel as though you are thinking clearer because you think of others as you program.
There is also an economical aspect to good communication. Because the majority of software costs are incurred after the software has been deployed (maintenance phase), you can cut costs by taking some time to write code that is easy to read so that you don't have to spend more time reading existing code to add value to it.
Eliminating excess complexity allows those who are reading, using, and modifying programs to understand them much more easily. While some complexity is acceptable given the complex nature of the specific problem at hand, it is best to remove excess complexity that does not add much value to the software. Communication and simplicity go hand in hand. The simpler the design of the system, the easier it is to read and understand. The more you focus on communication, the easier it is to see the excess complexity in parts of the system. In other words, "Keep it simple, stupid."
As addressed in the discussion regarding communication, the bulk of the cost of software is incurred after it is first released. Not only should code be easy to read, it should also be easy to change. Often times though, flexibility can come at the cost of increased complexity. Beck includes an example of this case by discussing user configurable options. The options provide flexibility, but it adds the complexity of the need of a configuration file. This extra layer of complexity needs to be considered when deciding whether the importance of software flexibility outweighs simplicity.
Understanding principles can provide general motivation or explanation behind a pattern, and as a guide when coming across novel situations. Below is a list of principles behind implementation patterns.
When designing a system, it is best to structure code so that any changes to a particular module will only have local consequences. Code that only affects its neighbors communicates well as it can be understood without having to understand the system as a whole.
Minimizing repetition contributes to the principle of keeping consequences local, as changes to one copy of the code will require a change in more than one place. Duplicate code is not the only form of repetition though. According to Beck, parallel class hierarchies are also repetitive, which breaks the principle of local consequences. In order to eliminate duplication, a good method is to break the program into many small pieces as large pieces of logic tend to be made up of parts of other large pieces.
Another principle that ties into local consequences is to keep logic and data together. By keeping these two entities near one another, changes will be kept local because changes in logic are often parallel to the changes in data.
Symmetry in code is where the same idea is expressed the same way everywhere it appears in the code.
Symmetry is also an important principle to uphold when programming. Identifying and expressing symmetry clearly makes code easier to read. As readers understand one half, it becomes easier to understand the other half if these two halves are symmetrical.
As another principle, declarative expression states that you should be able to read what the code is doing without having to understand the entire execution context. Here's an example below from the book using an old version of JUnit.
public static junit.framework.Test suite() { Test result = new TestSuite(); ... complicated stuff ... return result; }
What tests is this test suite running? At a quick glance, we wouldn't be able to tell unless we expand the "complicated stuff" section and dive deep into the code to fully understand what's going on. Beck states that JUnit 4 uses the principle of declarative expression to solve this issue, and includes the solution below.
@RunWith(Suite.class) @TestClasses({ SimpleTest.class, ComplicatedTest.class }) class AllTests { }
If it's known that tests are being aggregated using this method, then we just need to look at @TestClasses to understand which tests are expected to run in the suite. Not only does the TestClasses annotation provide flexibility for running tests than the code produced from the older version of JUnit, it makes the code much easier to read.
The final principle is to put logic or data that changes at the same rate together and separate those that change at different rates. Let's look at this code snippet below.
setAmount(int value, String currency) { this.value = value; this.currency = currency; }
Hypothetically, if a financial instrument can have its value and currency change together, it would be better to join the two fields together and express these entities as a helper object.
setAmount(int value, String currency) { this.value = new Money(value, currency); }
Which can then be further reduced as:
setAmount(Money value) { this.value = value; }
The rate of change principle is an application of the symmetry principle; however it is a form of temporal symmetry as these two fields change at the same time. Expressing this symmetry by making a helper Money object communicates the relationship of these fields to readers, and it can help prevent duplication and aid in consequence localization later down the road.
The values provide motivation for patterns while the principles aid in the translation of these values. This post has introduced the theoretical foundations of the implementation patterns that will be discussed in future posts. Stay tuned!