Part 3: Coding Guidelines
This series of blog entries describes a student project focused on developing an application by using methods like pair programming, test driven development and deployment pipelines.
An important part of any professional software development process (like ours 😀 ) are coding guidelines and methodologies, so we’ll deal with these in today’s blog entry. A fancy system we learned about in the course was the 12 factor methodology. It is mainly applicable for web-based, software as a service (SaaS) applications and defines high-level aspects an application should follow in order to remain robust and maintainable throughout its lifecycle.
The 12 factors, including detailed descriptions can be found on https://12factor.net/ . While our current project is not exactly web based, it should be interesting to take a look at the factors which are applicable to our game:
I. Codebase: One codebase tracked in revision control, many deploys
While our only deploy was basically the version of our code we actively developed, we used Git for tracking the code, respectively GitHub as a repository, as explained in an earlier entry.
II. Dependencies: Explicitly declare and isolate dependencies
Thanks to Gradle, we got this point covered, as all dependencies are listed in the build.gradle file. An example dependency in our case is JUnit. Gradle also automatically includes dependencies in its build process.
III. Config: Store config in the environment
In our project, the configuration for the game (for example the snake’s speed or the amount of food available) is stored in the file config.properties, which is part of the project folder and thus separated from the code. However, we didn’t store configs in the environment, apart from paths to Gradle etc in the initial setup. Our jar file creates the config file in the same directory, the first time it is executed, or if the existing config file is invalid.
IV. Backing services: Treat backing services as attached resources
Our game currently does not use any backing services/ external resources. However, a good example for such a service could be a webserver with a database which stores high-scores from different users.
V. Build, release, run: Strictly separate build and run stages
Again, Gradle saves the day by allowing us to automate our builds and include the config into the resulting jar-file.
The points VI through IX are specific to web based services and not applicable to our project, unfortunately 🙁
X. Dev/prod parity: Keep development, staging, and production as similar as possible
This is quite an interesting factor, directly related to continuous deployment:
Due to our group having only three people, the gap between developers and deployers tends to be quite small. By using Gradle (and Jenkins, later on) it would be theoretically possible release a new build after each newly developed feature.
XI. Logs: Treat logs as event streams
Writing events to stdout would be no problem in our case, however this factor additionally assumes a logging framework collecting these event outputs and writing them to a log file.
XII. Admin processes
Not really applicable in our case as well, since we don’t need any admin privileges for our game.
As mentioned before, many of the 12 factors are only really applicable to web-based services, so let’s take a look at some more general best principles for software development we tried to follow in our snakey project. (Many of the following ones can be found on http://clean-code-developer.de , which is a german site, unfortunately, but fret not, we’ll translate the important stuff here.)
One of the most commonly cited principles of clean code is “Don’t repeat yourself”, basically meaning not to duplicate existing code, because this can very easily lead to errors spreading throughout the code base and inconsistencies due to fixes for those errors, or other changes to the code.
Another well-known principle is “KISS” (Keep it simple, stupid): Code should be simple, easily understandable and follow a clear structure. Always favour an easy solution in order not to unnecessarily complicate code. It should be possible to understand and work with the code relatively fast – an important tool here are comments, which are often neglected. This is especially important in the age of continuous deployment, where bugs have to be fixed ASAP.
“Don’t optimize prematurely” is certainly in the top-3 of famous best practices as well. In most cases, the primary goal should be to produce readable code. Optimized code is often not readable. A hardware update may render your optimization useless and a waste of time (and money.
Especially when talking about OO-development, “Favour Composition over Inheritance” is often cited as composition is more flexible, testable and extensible than inheritance (you do know the difference, do you?).
Besides those fairly known principles, we also found lesser known ones, like the “Integration Operation Segregation Principle” (IOSP). It states that functions containing logic and operations should be separated from functions calling other functions. This results in shorter, easier to test code.
For achieving a similar result, “Single Level of Abstraction” can be used. In a given method, all functions should belong to the same degree of abstraction. Such code is easier to read and understand as well.
Compatible herewith is the “Single Responsibility Principle (SRP)”, which recommends to focus on a single responsibility at a time, i.e. one class or function should have exactly one clearly defined task. In case of changes to the code, it is obvious which classes have to be adapted.
“Separation of Concerns” (SoC) in turn is linked to the “Single Responsibility Principle”. Classes that have a single responsibility can still have multiple purposes, like logging, tracing, caching, persistency and so on. These objectives need to be separated into different methods or, if this is not possible, be clearly marked. In the best case each component focuses on one task, which leads to better testability and understandability.
“Source Code conventions”: Code is read more often than it is written. Therefore we need conventions that facilitate reading and understanding of new code. The main aspects that need to be considered are naming conventions and commenting. Agree on a consistent naming for variables, classes and methods. For example, we used underscore separation for our snake variables. No individual styles of developers need to be trained.
If you want to follow this path even further, you can aim for good code which does not need any comments. Mid-level quality code would have some comments to explain some complex dependencies in short. (In our snake project we only have a few comments 😀 ) As a guideline: Before commenting three lines of code, extract them in a separate method with a speaking method name.
Some more assorted practices we found during our research:
“Scout’s honor: Always leave a place in a better state than you found it.” -> Good developers leave code in a better state than in the state they found it in.
Root Cause Analysis: Healing symptoms is a fast treatment. However, in the long run, it’s more effort. Therefore, always solve problems by detecting its root cause and dealing with it directly. You can use this method simply by asking yourself at least five times “Why?”.
Use a version control system: Always use a version control system like Git, Mercurial or Subversion. This relieves the fear of breaking something when coding – the code can be reversed and you can learn a lot from mistakes.
Simple refactoring patterns: For code refactoring, use known patterns like those described by Martin Fowler. Their use cases highlight weaknesses in your code and encourage their usage because of proven additional value. For example start by renaming cryptical variables and methods into understandable easily readable code. Most of the refactoring has to be done manually. Some IDEs offer tools to support you.
Reflect on your progress: Implementing the Clean Code principles in your daily coding activities can be a long process. Therefore it is necessary to plan and progress in small steps and ask yourself at the end of each day: “How did I do my work?” The resulting answers can be very helpful for self-improvement and motivation and lead to a better learning curve.
Issue Tracking: The snake brain sometimes behaves like a sieve. In order not to forget issues, write them down. Prioritize issues and solve them one after the other. Tools can help here as well. For our project, we used waffle.io to track issues and tasks.
Automated integration testing: Do integration tests. It is a repetitive work so automate it with our favourite tools: Git and Jenkins.
Reviews: Showing your code to another developer can be very helpful for feedback and early error recognition (e.g. if the code is not matching the user requirements). Pair programming and code reviews are methods to escape the tunnel vision problem.
Read, Read, Read: Reading educates. Reading our blog is a good start. To keep yourself updated with the development of new techniques and it is recommended to read at least 6 books, papers or technical articles on subjects concerning your work per year.
Of course, there are many more code guidelines to catch up, if you’re interested in the topic. For Java, see:
For general advice:
(only in German: http://clean-code-developer.de/)
and of course the bestseller on this topic: Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin (including some mystical sounding guidelines like the “Law of Demeter”)
One last aspect remains for this long and winding blog entry: Static code analysis
For Eclipse there are several static code analysis tools which helped us finding stupid mistakes in our code like dead code, duplicate code or unnecessary assignments and other qualitative weaknesses (bad smells – please open the windows for some fresh air here). For eclipse we highly recommend FindBugs, Checkstyle and PMD.
They are all easy to use and Open Source, so install them via the Eclipse Marketplace and give them a try.
Don’t forget to like and subscribe.