1. Fixing a broken build is always the highest priority
Strictly speaking, no one should commit to this project if the build is broken, unless the commit will fix the build. Don't live with broken windows.
2. 100% Code Coverage
Line and Branch coverage should sit at at least 100% at all times. Having this level of coverage helps ensure that there are no "dark" areas of code that have been created "just-in-case". It helps ensure that code is accompanied by a test that illustrates a requirement.
3. Commit Often
The goal is to perform tiny changes at a time. A tiny change is a change that takes <15 mins to complete. Working like this makes the decision to rollback a lot easier. This puts pressure on your source control system, but in our experience it results in much better code. Reducing changes to this size is an art and takes practice but it can be done, and it is the way we do it round these parts.
4. If In Doubt, Spike It Out (On A Branch)
Sometimes some things are technically difficult. Not everybody is an expert at everything. The goal is to be able to fail fast and often. Like some Agile teams, we use "spiking" as a way of investigating a problem space. Whether or not a spike succeeds or fails (you decide), we want to keep the code and have the code separate from the HEAD.
5. All Prod Code Paired (Or Reviewed)
Preferably, all code is paired. We see the main benefit from effective pairing as sharing ideas and solutions about a problem in a way that is in keeping with these development practices. This might not be possible given the distributed nature of open source development. Maybe, maybe not. At any rate, all (test or prod) code changes will be heavily scrutinized, but don't worry the focus will be tearing apart our code, not your ego.
6. No Code Ownership
This is the idea that all contributors own the code. No one person will be responsible for the success or failure of any p
ortion of this project. This is something to remember when people are tearing your beloved code to shreds. You loved your code too much anyway.
7. Test Code Is Prod Code
Test code is a demonstration of functionality meeting requirements. It should clearly demonstrate what this project is about and what is possible with our code. Take the same sort of pride in it that you would with production code.
8. Fast (<3 mins) Builds
Our lives are too short to fail slowly.
9. Changing The Following Things Is A Team Decision...
- Reducing Coverage
- Changing Checkstyle
- Upgrading/Adding Dependencies
10. Code Is A Liability
No matter how maintainable your code base is, the cost of change is not flat over time. The more code you have, the more technical debt you probably create. Code is debt is a liability. Dig?
11. Molecular Over Atomic Tests
An Atomic test is a test that contains only one real implementation of a class, the test subject. The dependencies of the test subject are mocked out. They are extensively used (especially by pair programmers) to test drive out complex functionality within a single class. Molecular tests are tests that involve real implementations of more than one class. If you are clever enough to create a Molecular test that covers a logical group of classes while maintaining code coverage, you are able to refactor with a high degree of confidence within the classes covered by the Molecular test.
12. Use It Or Lose It
Sometimes we detect code "islands" within our code base. These are groups of classes that are 100% covered by testing but are never called by the main execution thread(s), and in themselves meet no current requirement. When we detect a code island, we delete it without (much!) fear.
13. Nulls Are Evil
You can significantly simplify your code if you stop nulls at system entry points and at returns from 3rd party code. We try to follow this basic rule. NullPointerExceptions indicate we have failed.
14. Statics Are Evil
Static method calls are too hard to mock out and reduce the overall testability of our system. We even hide static calls to 3rd party APIs behind the Java Boost auto-edging framework. You can look for our classes named *Static to see what we mean.
15. Use Delegation Over Inheritance
Mostly, we call a delegate rather than make use of inheritance to access behaviour. In our experience, the times when we have not followed this principle has led to code with a lower level of flexibility. Sometimes, inheritance is the best solution and we use it, but we find these cases are rare.
16. Create Small Classes And Methods.
We like to create small classes (<80 NCSS) and methods (<9 NCSS). We find that forcing ourselves (via checkstyle) to write code like this leads to more easily understandable code and a higher number of reusable components. Writing smaller classes and methods naturally forces them to be more focussed on doing one thing.
17. Use Outlandish Class Names.
A meaningless class name is often better than a meaningful one that has been misapplied. We relate to metaphors (especially "unprofessional" ones). For this reason, we try to avoid class names ending in "Helper", "Manager", "Framework", "Factory", "Processor", "Util".
Some More Development Principles That Need Explaining.
- No abstract classes
- Use "nu" instead of "new"
- No prod code just in order to test
- Avoid having packages and classes in the same folder
- Immutable over Mutable
- Injection over constructors with arguments
- Stateless over stateful
- Try to avoid java.util.Date
- Maintain field ordering
- Insert fixes as you go
- Maintain method ordering. Public methods first.