We all work in a world of limited time and resources. You may have been in a situation where a change in requirements took hours to implement just because, the same thing that needs to be changed is expressed in two or more places. If you change one you need to remember to change all the others as well which is what take hours.
At that time you must have surely thought: “Why isn’t there only one place where I could update the code and the change gets reflected everywhere required?” It becomes even more complicated when you forget any place where you need to change and in turn, your code breaks or bugs are introduced.
So, how to avoid such situations and make the wish of “I want to change the code at one place and have the change reflect everywhere required.”
The answer is: Don’t Repeat Yourself.
“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”
Most people take DRY to simply mean you shouldn’t duplicate code. That’s not its intention. The idea behind DRY is far more imposing than that.
Every piece of knowledge, in the development of a product, should have a single representation. A system’s knowledge is far broader than just its code. It refers to database schemas, test plans, the build system, even documentation.
If you have more than one way to express the same thing, at some point the two or three different representations will most likely fall out of step with each other. Even if they don’t, you’re guaranteeing yourself the headache of maintaining them in parallel whenever a change occurs.
And change will occur.
How DRY works?
DRY is an important tool in the crafting of flexible and maintainable software.
Most people assume that maintenance begins when an application is released, that maintenance means fixing bugs and enhancing features.
But, as Dave Thomas and Andy Hunt say in the “The Pragmatic Programmer” - all programming is maintenance programming.
If you look at the actual time you spend programming, you write a bit here and then you go back and make a change.
Or you go back and fix a bug.
Or you rip it out altogether and replace it with something else.
Maintenance is not discrete activity, but a routine part of the development process.
When you are writing code, you are maintaining code, even if the code was written just 20 minutes ago. DRY is a beautiful tool to make this process manageable.
We run an agile project similar to how one runs a product. We gather data, infer hypothesis and then run experiments to prove or disprove our hypothesis. Here, I will share my experience about experimenting with project processes on one of the projects at C42, and how we used velocity to measure and improve our delivery throughput by more than 15%.
In late 2012, I rode a motorcycle on a race-track for the first time after riding on the street for more than a decade.
What I discovered as I studied the art of racing a motorcycle was that several parallels could be drawn between motor racing and writing quality software.
I'd like to share these with you in a series of posts covering the following topics with this post being the first:
Focus is a currency
Go slow to go fast
Constant analysis of your ride
Focus is a currency
Focus has a budget.
Every individual has certain amount of focus. Every decision you make consciously spends some of this budget. Once you choose to spend it on a particular aspect of the problem, you only have so much left to spend on other aspects. As a corollary if you spread your focus too thin, none of the tasks at hand will be done to your fullest satisfaction.
On the track
Riding a bike is a complex processes. There are a lot of variables you have to juggle and make a split second decisions as you approach a tight corner at a high speed. When to switch gears, when to brake, how much to brake by and so on.
If you try to juggle all these balls at the same time, you will invariably make a mistake and either mess up your lap times or worse, crash.
The first piece of advice given on to us on the track is to focus on a single skill at a time during practice sessions. Internalise and hone that one skill. Ignore the rest, even if overall lap times suffer while you learn.
For example, the moment shifting gears becomes instinctive, the attention you have to spend on it significantly reduces leaving you free to focus on other something else.
To learn effectively, improve one skill area at a time so that it no longer requires conscious thought. Then move to the next skill.
While building a complex piece of software, we face similar problems. We have several skills we need to deploy to solve the problem and hundreds of decisions to make daily.
We have to juggle functional and non-functional requirements, legacy, technical debt, overall system architecture etc. while also paying attention to code quality and release dates.
To effectively work under these constraints you can employ certain tools and processes to conserve focus for important decisions.
Know thy environment
Tracer Bullets & Spikes
Know thy Environment
We use different tools to codify a solution: language, libraries, frameworks, console, REPL, editor/IDE to name a few.
It is important to master the programming environment. The environment should facilitate you to seamlessly transfer your thoughts into a code and be unobtrusive in the process.
The less you know about your environment more time and focus you'll spend making decisions around getting simple things done. This will distract you from your core thought process - the solution.
Forming layers of abstraction is a key tool to maintain focus on a specific part of the system.
I deal with a very specific slice of the system at a time and make sure every small detail in that layer is dealt with properly. Equally, I minimise the amount of focus I spend on layers other than the one I'm working on at the moment.
If, while working on one layer, I'm forced to maintain focus on many aspects of other layers, I know I have leaky abstractions that are draining valuable focus away from what I'm working on.
TDD, BDD and friends
Another nifty tool which helps me focus my attention on a small chunk of the problem is writing tests first.
It doesn't really matter whether it's TDD or BDD or some other flavour thereof. Writing a small concise test helps you pay close attention to every aspect of a small problem at hand - one failing test.
If I simply can't write a small, focussed test, then once again I know I have leaky abstractions.
Tracer Bullets & Spikes
There are situations when either the problem, the potential solution or both are unclear. In such scenarios a spike helps.
A spike places focus on a vertical slice of the system and you can implement that slice without worrying about side effects and edge cases. Carving out a vertical slice essentially means ignoring most of the system and deliberately focusing on a single aspect of the system.
A spike typically results in better understanding of the problem at hand as well as potential solutions which lead to better abstractions.
A Tracer bullet is a tool similar to a spike that's also worth looking into.
We programmers have to make a lot of decisions while codifying a solution.
To do it well we have to pay close attention to where we are spending our focus. Having a robust tool-belt with well understood practices and processes helps us manage our focus budget better, leading to greater attention to detail where it really creates value.
Niranjan is a founder and partner at C42 Engineering. If you liked this post, please consider...
Software with any complexity requires careful design to ensure readability and maintainability of the code. The two extremes to achieve a good design are upfront design done on a whiteboard before writing a single line of code or evolve it by iterating over the code by adding functionality one at a time. Which begs the question, which approach leads to better design or if there is a middle ground.
Like most of the questions of this sort, the answer is 'it depends'. What really matters is the state of the codebase when you call it 'done'. As a rule of thumb you should ensure that your code adheres to simple rules of design (Xp Simplicity Rules) before it's complete.
There are various factors which influence the choice of methodology used to solve the problem. To list a few:
Understanding of the problem space - it includes domain and non-functional requirements such as scale, usability and so on
Familiarity with the solution space - how well you know the toolchain used for solving the problem such as language, libraries, framework, third party apis
If you have intimate understanding of both the problem space and the solution space, you can jump right in and start designing your system by defining various layers of abstractions and how they communicate with each other.
Once the basic skeleton is in place, pick a part of the system to flesh out in greater detail. You can mock/stub most of the other abstractions at this time and work on a single piece of the puzzle. This allows you to focus on a part of the problem and solve it well while knowing how it fits into the bigger picture.
It is important to have a good understanding of the problem and the solution space to create a scalable upfront design. Building abstractions without understanding the problem and solution space can lead you to suboptimal solution as it locks you down to a particular implementation detail - Premature Generalization Is Evil.
On the other hand if you are in an exploratory phase, trying to create a design upfront can distract you from the actual problem at hand. You will end up spending a lot of time thinking about what are the right abstractions for a given problem space or how to best utilise the language features to create a terse implementation.
To avoid such diversions, it's better to pick one simple flow and codify it the best you can and build on top of it till you have enough understanding of what the solution should look like. This piece of code acts as a Tracer Bullet indicating how close you are to the target. Once you have sufficient understanding of the problem and the solution space you can refactor the code to improve the code quality.
Another nifty tool often employed to evolve the system design over time is TDD/BDD. Test driven development helps you split the problem domain into smaller chunks while defining the interactions between various objects by defining a concise API. It allows you to focus on a small subsection of the problem while providing a safety net to ensure that system as a whole is working as expected.
Which is Better?
Unfortunately there is no panacea when it comes to software development methodologies. Every situation is unique so is every team. Depending on the problem at hand and expertise available the team needs to set appropriate pace. More often than not, a project will oscillate between these two extremities during it's lifecycle.
These are the practices you should follow for robust and well maintained code.
Single Object Responsibility
In object-oriented programming, the single responsibility principle states that every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility. The reason it is important to keep a class focused on a single concern is that it makes the class more robust.
"There are only two hard problems in Computer Science: cache invalidation and naming things.” - Phil Karlton.
The importance of well-named classes and methods can't be emphasized enough. When another developer (or you, a couple of weeks down the line) is reading the code, the names should make it obvious what the class / method is supposed to to. At the very least, keep away from cryptic abbreviations, as illustrated by Steven Deobald here.
Single assertion per test
Tests are easier to read if there is a single assertion, that tests a particular aspect of the functionality.
The above test can be split up into the following
Rails Best Practices
Skinny controller, fat model
Push all logic to the model.
In fact, there are standard patterns available for each of the 7 RESTful actions, so try to fit into one of them.
Use ! as a name for any method that saves in the database
By convention, ! is used for any ruby method is dangerous in some way. One dangerous effect that we often care about is that modifying an entity that will be persisted.
When to use this also fits in to transaction management, and these are decisions that should be made on a case by case basis.
But, for the most part, it's usually a good idea to name methods that do a save in the db as a side effect with a !, and use either exceptions or the errors collection to handle error scenarios.
Do not use RJS
This is bad for multiple reasons:
Security implications of eval() on the client side
Debugging requires you to keep a lot of context about what state the dom is in. In the above example, it is important to know that the .story div is hidden
The API that we have written is pretty much tied to a particular page. If i'm calling my API from another screen, I will need to create another div with the same class (story)
Use handlebars to render AJAX calls
Maturity for AJAX calls is as follows:
Worst: Use RJS (see above)
Bad: Render HTML in the output of the method, and just attach that HTML to the DOM
Good: Let the API return some JSON. Use Mustache or underscore to render
Have a very RESTful API
REST is based on a PHd paper by Roy Fieldings.
Borrowing from REST, the rails world has evolved it's own set of best practices around how to build a URL. One thought is to have every URL be one of seven standard actions. Every modification of a resource can be modeled as a CRUD operation on another resource.
can be remodeled as follows
In the latter case, we are creating a cancellation resource within an order. Internally, the service will cancel the order as the cancellation object is created. The cancellation object may or may not need to be actually stored in the database, or even modeled. The cancellation object is a facade to the external world, to help any HTTP aware client understand your interface.
Remember that once you expose an API, you will have to honor it. Be very conservative, especially when exposing nested resources, and belongs_to relationships.
For most apps, you probably want to handle transactions at the controller level
For most CRUD operations, you probably want the entire operation to happen, or not happen. Transactions are something that should be studied on a case by case basis. However, for most cases, the easiest place to start and finish the transaction is at the controller level, so that your model is not littered with it.
Use settings logic to push configuration into files
Settings logic is a great way to have configuration outside of the application, and is a fantastic alternative to using global variables.
Especially when you are doing a feature toggle, or something else of the sort.
Not every model has to be database backed
One common anti pattern that is often observed is to have every single class inherit from ActiveRecord::Base.
This is clearly a no-no. Like with every other programming language, in ruby you will need classes to encapsulate various behavior that does not need to be persisted to the database.
You can include ActiveModel into any class to get some of the nice things you get from ActiveRecord, like to_json, etc...
Controller tests should include an assert on the status code
This will provide you with a helpful failure if your test fails due to a permission error / internal server error. Thus you readily know that the failure is due to something outside your actual assertion, rather than have to figure that out working backwards from a failure message related to your actual assertion.
The failure here doesn't provide a clue that the problem lies in the test setup. But when you assert on the expected status code: