article
- Choose good names. π
- Spot places where knowledge about something does not belong.
- The code won’t be too complex. Because your static analysis tools tell you if your cyclomatic complexity metric is too high.
- Classes and modules will have low coupling and high cohesion. This leads to code that’s more easily testable and has higher reusability than other code.
- If you have an error if explaining comments are missing, you could make sure that your developers take some extra time to make sure code can be easier to understand. Other rules, like variable and method/class/module naming conventions, have the same goal.
Quick wins, part 4: YAGNI
We refactored some code yesterday, to move knowledge about an implementation of a function back to where it belonged: Into the function’s class and into the function itself.
Today I want to talk about another topic that often comes up when you are refactoring, or plainly “changing code.”
It happened the other day, during a workshop I was doing on software testing. The participant wanted to apply his new knowledge and write tests for a given JavaScript class. He had written the code a few weeks before we did the workshop. During the hands-on part of the workshop, he wanted to add tests, to make sure everything worked and to make sure that he understood what I had been talking about.
We were lucky in as so far that something happened that usually happens next: He noticed that his code was not easily testable. The design of his class made it harder for him than he would have liked. We talked about the problem, and he noticed the source of it. His class had too many responsibilities. He extracted the code in question into a new service and could then mock the new service when testing his original class. This was good. He was ecstatic. He made progress!
Having gained so much momentum, he went overboard: During his test-design, he wanted to be too clever and tried to write an elaborate test setup which was to be reused between different test runs. It was supposed to be a reusable, parameterizable do-it-all function that could setup the tests just right. With no duplication. In short, it was so much code with so much logic that it would’ve warranted its own tests. And the worst thing? It didn’t work and made trouble for writing his tests.
I was a bit thankful because that gave me the opportunity to tell him:
Premature optimization is the root of all evil.
Perhaps you’ve heard that saying already. Donald Knuth coined this phrase. There’s more to it, but that could be discussed in another email.
Back to my tester. After talking about the problems his function gave him and the difficulty in getting it right he settled for the simple solution: Write your tests. Accept duplication. Keep it simple, use copy & paste if it makes you faster and is more convenient. Write the tests you need and keep them green. And after all that, and only then, refactor your tests to remove duplication where applicable. Don’t try to write the perfect code from the start. Let the design evolve with the help of your tests. Don’t be afraid to make baby steps and don’t expect to have perfect code after the first try.
I hope you liked this series. Perhaps you could take something away from it. If you have questions, let me know.
Tomorrow I’ll be off; it’s a holiday where I live, and I’ll use it to spend time with my family. See you on Monday. π
Quick wins, part 3: Keep it local
Yesterday I closed with this idea:
Spot places where knowledge about something does not belong.
What do I mean by that? Sometimes I come across some code that does not read right. I will use a pseudo code example to illustrate:
class Foo
def initialize(bar_service)
@bar_service = bar_service
end
def quux
if @bar_service.greeting == "hello"
@bar_service.greet("goodbye")
else
@bar_service.greet("hello")
end
end
end
class BarService
attr_accessor :greeting
def greet(message)
@greeting = message
end
end
What bothers me with this code? The method quux has too much knowledge about how the @bar_service works. Foo.quux knows that the @bar_service has an instance variable called greeting and at least one specific value it might have ("hello"). It also knows two values that the greet() method might be called with.
Now it happens that this knowledge about how the greeting and greet work, is also spread into other parts of the application. What happens if you need to change something about the greet() method? You have to find every place in your application and update it to reflect the new changes.
This isn’t good.
There are places like this inside many applications. You might need some practice to spot them, but with some practice it becomes easier. For this example I would like to suggest to move all knowledge about how greeting and greet work inside the BarService. Start with the conditional, like this:
class Foo
def initialize(bar_service)
@bar_service = bar_service
end
def quux
@bar_service.greet
end
end
class BarService
attr_accessor :greeting
def greet
if @greeting == "hello"
@greeting = "goodbye"
else
@greeting = "hello"
end
end
end
Now we are free to change the internals of the greet method. We could add a third option or change it completely. The class Foo does not need to change at all. It continues to call greet as if nothing has happened.
One of my overarching topics is testing. A refactoring like this should be covered with tests. Not only do you need tests for the Foo class, but also for how BarService.greet works. And for every part of the app that interacts with either.
Tomorrow we’ll look at another way to do a refactoring.
Quick wins, part 2: Method names revisited
Yesterday, we had the first part of this series on quick wins and simple steps to improve your code quality. It was about naming β specifically variables and method names.
Two things were not 100% right in these examples.
The first error
You might have noticed that my loop examples were written in Ruby code. Yet the method name doSomething was written in camelCase. This is unusual for Ruby code where developers tend to use snake_case for method names.
I did not lint that email. Hence no robot told me about my error. I believe it is a good example of the benefit of automatic linting. This error would have been found. If you read the code and were put off by this naming scheme, you even experienced why conventions and rules are necessary: Because coding by the rules helps developers to focus on the semantics of the code, not the syntax.
The second problem
Do you remember that I wrote about JavaScript loops and gave the classic for loop example? I complained about the i variable and that it should be called iterator. Perhaps you did not like this idea and my example? Let me take a step back for a second.
When naming variables and method names, you have to make sure they “speak.” The names should indicate their meaning and make it easier for another developer to understand the semantics of your code. Yet, when you are fully aware of the problem domain you code deals with, it can happen that you try to be too specific. If you are, you tend to use long, verbose names for variables and methods. An example could be this:
A common value for the allowed length of method names is 30 chars. The above example could be broken into two methods.
The next possible quick win might be to think about the existence of these methods inside the Article class. Verifying and fixing meta tags surely does not need to be done inside this class. If you want to follow the Single Responsibility Principle, you should make sure that the Article class does not have a reason to change, if you decide to change something about verifying and fixing meta tags. Rather the Article class might change when you decide that an article should have a mandatory sub-headline.
Back to the iterator. This name could pass the test for too specific. Only, when i refers to a variable that is declared and initialized outside of the scope of the loop, does it make sense to choose a different name. Or doesn’t it? As always it depends. The classic for loop is taught in almost every book on JavaScript, and it is easily identifiable as to what it is. But there might be reasons to deviate from that, as I indicated above.
Conclusion
To summarize:
Tomorrow we’ll look at another example for 2 and a practical idea of what to do about it.
Quick wins and simple steps for improving the quality of your code
Good software needs good code. If you want to achieve a high quality in what you ship, you need to care for the quality down to each method you write.
I want to use this week to write a small series on techniques and ideas about how to increase your code quality. When I look at code, it is often possible to find spots in the code, where a simple change can be made. In some cases it’s even an easy tweak. Some of these examples will come from the the actual code that I worked on. Others will be created by me, for this series. You won’t see any code from my clients, of course. The only thing I take from them is the inspiration. And money. π€£
Naming
A good place to start with is to look at variable names. If you have a call to .map() or .each(), then take a look at what you are iterating. Is is a list of Book objects? Then you should call each item that you are iterating what it is, book.
# this is not good
items.map do |i|
i.doSomething
end
# this is better
list_of_books.map do |book|
book.doSomething
end
This would take care of the naming of some variables.
In classic JavaScript loops, you often see a variable called i:
for (i = 0; i < array.length; i++) {
// something happens here
}
Well, what’s this i anyway? If it’s an iterator, why not call it that? Even worse, when you sometimes combine i with a jand a k:
for (i = 1, j = 0, k = 150; i <= 5; i++, j += 30, k -= 30) { /* do work */ }
(This is copied from a SO answer)
I bet you a non-trivial amount of money that you won’t be able to tell me without looking it up what these variables refer to 9 months after you wrote code like that.
Will it take a small amount of extra time to come up with a proper name and use that instead? Probably. Will this extra time be saved every time a(nother) human reads that code? Hell yes!
A possible next step would be to change something about the doSomething() method. What the hell does it do? Why doesn’t it tell us already from its name?
In this case? Because that’s just pseudo-code for you π
But please make sure that you use proper and valid names for your methods and variables.
Power Laws
My work as a consultant offers me the opportunity to accompany a team for a certain amount of time. I join them, we work together, and then I leave again. Our time together gets extended, sometimes. This model has the benefit that I get to know a lot of people and teams β and how they work.
Do you know the Pareto principle? It’s also called the power law distribution or the 80/20 rule. A simple explanation would be that 20 percent of the people own 80 percent of the wealth of the whole world, which makes it relatable and understandable. Only that it’s wrong. By now 10% hold 90% of the wealth already. And it’s getting worse. I don’t have any sources on this right now, and I won’t go looking. Because it was only meant as an image of how this works.
Back to my clientsβ¦
There is a similar distribution of 80/20 to be found. 80% of software development teams do the same mistakes over and over again. It starts with a new green-field project. A year of development work passes. A lot of code was written. And after a year the team is frustrated with their software again and doesn’t find a way out. This is bad practice.
If you find yourself in these situations, there are ways out of it. With a lot of intrinsic motivation and the ability to learn from mistakes and external sources, you could be able to drag the ship around and sail into the sunset, happily. But there are a lot of rocks under the water that might wreck your boat. An experienced navigator for these waters could prove beneficial.
In my opinion, a good start is to look to industry standards and follow those as well as common best-practices. Find and learn the rules on how other teams work. They might seem strange; you might not understand or like them. But let me tell you something: They way you worked up until here didn’t work and brought you into this mess. Doing things as you’ve always done them won’t help you a bit.
So, don’t be smart. Find rules. Follow the rules. Stick to them and don’t deviate. Re-evaluate in 6 months. It will hurt. It might not be fun. But it will get better.
If you need a pointer, let me know by replying.
Refactoring without a care
Before I get to today’s topic, I would like to say thank you, to you. My little poem yesterday seemed to resonate with you. At first, I planned to write about it and its meaning today. But your responses indicated that it spoke to you. And I wouldn’t want to ruin this with my ramblings about it. So I’ll just finish with: I enjoyed this very much.
Lately, I spent some more time on Twitter. I don’t know how to use Twitter well (enough). I always have trouble with creating threads or topics. But I (re)discovered some very interesting people, sharing their ideas in long threads.
A few days ago I came across @GeePaw Hill. I believe it was because I followed a few tweets by Kent Beck. GeePaw Hill had this thread where he encouraged people to refactor without caring for the application domain, only for the code. You can read the thread here. He even elaborated some more on his blog.
I find the idea fascinating and will continue to think about that.
A neverending story
Back then; I did it; I liked it much; Found it a necessary touch;
Never it challenged, I; then saw; A source without it; didn’t look too raw;
Since then it flows without it well Some purists call it living hell.
A neverending story
Back then; I did it; I liked it much; Found it a necessary touch;
Never it challenged, I; then saw; A source without it; didn’t look too raw;
Since then it flows without it well Some purists call it living hell.
Computer says no
[β¦] we do have formal rules that we should obey when writing code. A team has rules, and new team members need to learn them before trying to write any code.
That’s what I wrote yesterday. It’s my email so I can write whatever I think is correct. You’ll let me know through your answers if I am wrong.
My friend Tino answered on Friday and asked whether a university degree or certificates might function as a driver’s license. And that is true to a certain degree. I am getting a new certificate these days as well. I hope to complete the exam on Wednesday (ISTQB Advanced Level β Technical Test Analyst).
The obvious difference to a driver’s license? I am not legally required to obtain one before I can start writing code. Tino also said that he’d find it interesting to be (self)tested in current web-standards and best practices. I do believe these tests are valuable. If I come around to create one, I’ll let you know.
Back to the beginning of the email. Why do rules matter to a team? Developers have their style for writing code. Even if there are rules and certain regulations you have to follow, developers still find ways to write code in their unique style. And that’s a good thing. It would be boring otherwise. Still, this style has to obey the rules. Here’s why:
In short: Rules help your team to write code that is maintainable and has low technical debt. This reduces the total costs of ownership. If you only look at the cost of writing the code and delivering the software, the costs might be higher if you follow stricter rules. Over the complete lifecycle of a software (product), the total costs would be lower because of better maintainability and a lower number of defects.
This is already getting long. See you tomorrow with even more thoughts on this topic.
Driving on the left side of the road
We don’t have rules of the road for software development. You don’t have to stop at every red light or keep your speed below a specific limit.
Well, yes. We do have rules. If you write your whole program in only one file, someone will tell you that this is bad. At least I hope that’s the case!
If you only use variable names like x or y, your coworkers will flag this during code review. Perhaps you already have static analysis tools that tell you before your coworkers do?
While we do not have a driver’s license, we do have formal rules that we should obey when writing code. A team has rules, and new team members need to learn them before trying to write any code. Otherwise, it could feel like driving on the wrong side of the road: Driving on the right side of the road feels natural to you if you’ve never done it any differently. But it can have dramatic consequences if everyone else expects you to drive on the left side.