What is this post?
What I'll attempt to lay out here has been said with eloquence that I'll never be able to approach; detailed in ways I will never come up with; and I assume by people who've forgotten more about good software development than all my experience has ever shown me.
This is not a final word or master reference for software development. It's my mental dump so I can google my brain later.
This is my approach to software development; my considerations as I strive to be a software craftsperson.
A lot of my thinking has been focused by Fred George . I had his code boot camp and it crystallized a lot of the ideas and concepts I had hazy concepts around of how to use from what I'd read.
Before the pivotal training; I'd watched a lot of his talks on YouTube. It started a process of thinking about coding vastly differently than anything I'd considered before.
I started to understand design patterns in a new way. There's some patterns that worked well in a primitive style of programming - others, I could never find how they improved the design and architecture; this is because I don't have a grasp of being a software craftsman.
I have an (glimmer of?) understanding now; and the development will never be the same for me.
How I need to develop software had been crystallized and I can never go back.
I've tried to hack things; it bit me hard; now it's all (trying to) following these ideals.
There is ebb and flow depending on the exact project; but I always look to apply my software craftsman principles.
At a high level; I think I can narrow the list of principles to a few things. Sorta...
- Extreme Programming
- Beck's 4 rules of simple design
- Object Oriented Design
- No branching
All of these are super sets of practices that, IMO must be applied.
Let's do the shortest one first. No Branching.
That's really all there is to it. Time to move on... Oh... I should add some details... OK
What kinda branch flow will you see on a normal program? 'if' and 'switch' are the two standouts.
These are code smells. Do not have them in your code!
When I first heard this in a talk by Fred George; I... Uhh... What's a word for the sound of disbelief made in the back of the throat? 'Cause I did that.
But... I'm not ignoring something because it makes no sense; I assume I'm wrong and look for why.
I thought about it and how Fred claimed 3/4 of the GoF are about how to get rid of 'if' statements.
I thought... and I pondered... and it started to make sense... Many design patterns started to fall into place; I started to understand how these patterns applied. They require an effort to not have branching; branching makes some patterns redundant.
I have been striving to eliminate branching from my code. This is my new world; this is how a strive to be a better software craftsperson.
There is an exception to having 'if's in the code. Just 'if'; not else. The exception is for 'guard clauses'. Swift made guard clauses explicit; so for a Swift codebase; I'd expect ~zero 'if' statements.
OK - the is one other exception for 'if' and even 'switch'. Translating from a boundary. A database storing
ints to represent what the system should do. At the database access layer; this gets translated into the behavior. These boundary layers are allowed branching to configure the behavior.
For any following my posts; this is represented as the mediator layer in the VBM. It configures the behavior for the screen.
This approach can be seen in some of the refactoring of my Android Logger.
4 Rules of Simple Design
Let's go with Beck's rules next. As cited by Martin Fowler these rules are:.
- Passes the tests
- Reveals intention
- No duplication
- Fewest elements
These are part of extreme programming; or the come out as a simplification of what XP encourages.
Passes the tests.
To pass the tests; you must have tests. I'll have a bit to say about TDD when I get into the Extreme Programming section; we can summarize that I'm a fan.
The tests aren't just to prove the code does what you expect. They are to help drive other fundamental aspects of code craftsmanship.
Small methods; it's easier to test when it didn't do much.
Single responsibility: the tests start to smell if a class does too much.
Class decomposition: Part of. Code craftsmanship is the object oriented design; which I'll go on about a bit more later; but testing will highlight bad design by forcing a bunch of configuration unrelated to what's being tested. ie - setting a user's address to determine if the name fields are invalid. These aren't related...
Properly TDD'd code has test coverage sufficient that any code being removed will break a test. Application boundary layers get a small exception, but not much of one.
If deleting code doesn't break a test;
How Can Others Know The Code Works?
The answer is - They Can't.
This has an effect of pushing towards 100% code coverage, but it's not the coverage number that matters. It's that all code has tests that demonstrate it does what is expected/intended.
There will be lines that are harder to test; particularly error cases; but harder isn't impossible. If it isn't tested; how will you know it works when it is supposed to??? See above.
The simplest version of this is "self documenting code". Which is close; the code should reveal it's intent. A method called
SumAndMultipleThenRemoveAtIndex is self documenting. It doesn't reveal intent though. It reveals the implementation details. What is the intent of the method?
If you write the test first; you'll drawn to naming methods around the desired behavior; not implementation.
This is not an advocation of Behavior Driven Design (BDD); I'm against that driving the code; but the behavior of a method is the what it's doing; we can not care about the how.
Another aspect of the practice of having the code Reveal Intent is that comments in code to be a code smell. I've heard comments referred to as a deodorant. A comment is oft put in as a cover for a bad design or hacky code. The code doesn't reveal what it's intent is; and as such needs explanation to be understood.
Or as a co-worker, Steve, is trying to beat into my head, "Comments are either a lie or an apology". So yea - Comments are deodorant, a lie, or an apology.
Things should be named for what behavior or result is accomplished. Using the
SumAndMultipleThenRemoveAtIndex method as an example. Without knowing what lies I made up with that; I can see it renamed to
Remove. That's what it does. How it does it; that's implementation detail and should not be exposed; even via method names. (it's a hack example; I'm being lazy)
This type of naming is for Classes as well. They should do one thing; the name should communicate what that thing is.
Too often I see things put in because "it's just one field" and it is what introduces a small rot to the system. The "just one" sets a bad example, and it will spread.
Pair Programming and having code reviewed is fantastic for measuring how well code reveals intent. If the pair or reviewer is asking questions; that's an opportunity to increase clarity in the code. The code can be refactored to improve communication.
This is the long quoted DRY idea. If you're copy & pasting code to tweak it slightly; it's probably going to be done a third time, so try to make it generic.
I do like to wait for the third example; but if there's an easy/obvious way; two is plenty. One is not enough.
Code Analysis tools are getting pretty good at detecting similar blocks of code and will point them out. These are things that will likely have high value in refactoring into a common source.
That's really all I have for this; Code should do only one thing; and that one thing should happen in only one place.
Writing good code is hard. Writing little code is really hard. Simple design is not easy.
This is the last of the 4 simple rules; and they're in (general) order of importance.
If it hinders revealing intent; leave duplication. Other than that; strive to simplify the code so that there is the least amount of code capable to performing the functionality required. Less code to maintain is less code to have active mentally; and less code to test.
As we get more skill in producing a simple design; the design stays flexible. Each line of code ends up doing more; but still just one thing.
It's an amazing world when code starts to shrink as features are added. Duplication is removed; commonality recognized. Branching eliminated - These all drive towards having the Simple Design; and it's going to be smaller than any other code to accomplish the same thing.
That's it for now. We'll move onto XP Practices and Object Oriented Design in future posts.