I've often wanted to talk to people about things like these and I'm often pushed back by people saying "This is simple, we all know this" but I don' think that's the case. If anything I think we know very little about how to keep software clean and simple. I've been trying to change that in my works by having people comment on my code that I've recently written for homework but people aren't really up for the idea.
I've had one discussion that stood out. We were talking about functional programming and I said that the idea of functional programming was the segregate state to the smallest unit of computation that must operate on it. I was met with my "friend" just blindly telling me to "stop saying that". I asked for a counter argument to that statement but they just said that I should stop saying that.
We as a community are horrible at speaking about code quality , evaluating each others work, and even just sifting out opinions from facts. It's crazy and it's something that needs to change if we want to take our field to a future where we can all be happy with the code we are writing.
I've got some suggestions I'm happy to talk to others about. My email is in my about page on this website. Please comment here or email me and I'd like to talk about this!
Something that lists like this always seem to miss is locality of reference. You shouldn't have to jump around all over a file (much less through several files) to follow the code that does a single thing.
The single responsibility principle should really be "exactly one" rather than "not more than one". If a function is responsible for less than a whole thing, it shouldn't be a function on its own.
>> Can be easily extended by any other developer
This is a tenet that will lead inexperienced developers astray. This "rule" is just too ambiguous. Extensibility is a fascination for object-oriented programmers but in my experience doesn't have a lot of successful examples. Typically I have seen this manifest itself in either a Command+Composite style where every meaningful class is a composite of other SRP classes, or in a proliferation of interfaces that seldom have method definitions and are instead used to enforce coding standards or dependencies.
KISS is incompatible with this rule and you should kill this rule with fire because simple is not extensible. Perhaps when the goal is extensibility then should you consider other developers, but if you are developing a "beige" application then you should not consider extensibility. Instead, just assume that release management will handle changes, i.e. another developer will update rather than extend your class and that will be released in version 1.1.
Of course, to do this also means admitting that version 1.0 of your class was pretty much garbage and that it needed to be "extended". Tough pill to swallow for some.
While some of these points have something to do with 'clean' code or maintainable code or whatever, this copy-pasted list of very very shallow statements about code isn't really useful. I expected there would at least be a few examples on how to implement these principles, perhaps in a 'before and after'.
Maybe there should be a 'click-bait' button on HN with which we can report things as such, along with posts such as 'Why I won't be using popular-technology-x ever again' and '10 things I hate about SQL'
> 3. It should not be redundant
Not sure I agree with this one. While abstractions are a great way to reduce the length of code, sometimes they break readability. When you read code, sometimes, you feel like you don't read a solution to your problem, but a way to solve your problem masked behind abstractions far removed from the domain concepts.
That's why, sometimes, redundancy is better than the wrong abstraction.
I believe an important quality is that code should TELL A STORY.
Consider a biography: you could simply collect facts about a person and write them in an arbitrary order and call it a biography. It could be a complete and accurate account, and still be impossible to read or follow.
Well written code is not only complete, but it also guides the reader through the logic.
Consider the difference between:
statuses = []
reporter = Reporter.new
jobs.each do |job|
statuses << job.complete && !job.error
end
and job_statuses = jobs.map do |job|
job.complete && !job.error
end
job_status_reporter = Reporter.new
In the first case, we see statuses declared. Statuses of what? Not yet clear. And the code that updates it is separated by unrelated code. Also, what will reporter be reporting?
In the second case, map and better naming are used making it clear that we are getting a status for every job. Aha! I don't even need to look at the implementation of the do block to understand what's happening.Clean code isn't always the most efficient code, but I think that's fine. Many times in my career I've found that you don't always need the most optimal, efficient, and performant solution to get the job done, and that sometimes you need to sacrifice those attributes for clear, readable, understandable code. Good architecture usually supercedes speed of individual algorithms. Hence why you see almost every major language under the sun has been used at scale.
I remember when looking into neural nets, the basic python code to get one running was super easy for me to understand. However, I also realize that the optimal, most efficient methods of neural network libraries are way more complicated (and for good reason).
Most of all, clean code should be easy to reason about.
Extensibility is important in some cases and a detriment in others. Most of the rules about extensibility came from environments where the code was running in many places at the same time (ie delivered to customers).
In a SaaS environment the code needn't be extensible as there is only 1 copy of it. It is much more important for the code to be changeable, rather than extensible and in many cases the things you do to make code extensible make it harder to alter fundamentally.
Its important to understand that how you deliver your software is one of the biggest guiding factors in how you design your software and take that into account.
If written by someone else, clean code is: all and only the code that I personally won't have to maintain :-)
Something I've noticed, in bad code if you need to learn something, or change something... Simply "searching" the code isn't enough. Maybe there are multiple places that do a similar thing, maybe the names are weird, maybe it's so condensed as to be unreadable. Whatever the cause... The only way to find the piece doing the thing is to step though it.
Good code, you can search it... even if the majority of it is unfamiliar. Find a piece, and say "oh this is probably the spot" with out ever executing it.
I recommend "What Could Possibly Be Worse Than Failure?" by Alex Papadimoulis (http://thedailywtf.com/articles/What_Could_Possibly_Be_Worse...).
An excerpt: We are practically the only industry where completion and success are synonymous. If the foundation of a one-year-old home is crumbling and its roof is plagued with leaks, would anybody actually call that a success? Despite being filmed and produced on budget, is there anyone who would not be ashamed to have Gigli on their filmography? Of course not! So why are the products we create – complex information systems that should last at least fifteen years – be held to a different standard?
Now think back those projects of yours. How many would you say are maintainable by someone with less business knowledge and a weaker grasp of the system’s design? How many will not snowball into an unmaintainable mess? How many do you truly believe could last fifteen years? I’ll bet that number is quite a bit lower than all of them.
Being able to simulate amnesia is my goal.
I write code that I come back to and don't understand, and realise its terribly complex because I understood everything at the time, but could not imagine what it would be like coming back after a month to make a change, or for someone new to try to understand it.
Code is for people to understand.
Some people think its for being able to write good tests, eliminating all side-effects and shared state, for the computer to be able to run quickly and optimise, to be easy to read.
But its really just about being able to be understood. Most time will be spent in maintenance.
When you modify and debug, you are diving into the middle. You are not walking through the code base from the beginning and reading all the comments.
It needs to be understandable from all locations.
I'm a strong believer in micro-modules. Left pad et. al. I try to create small modules which do one thing well that I can trust and not have to think about.
If I'm remembering correctly this article seems to mostly paraphrase what the various developers Robert C. Martin interviewed for this book ("Clean Code: A Handbook of Agile Software Craftsmanship") said...in mostly the same order.
Someone very experienced once told me: "For one change in requirement there should me one change in your code. That's a clean code. If you can achieve it without any change in code, it's even better."
This quote always sticks in my mind :)
>> 5. Can be easily extended by any other developer
I can remember a instance, when I started my carrier. I was a JS developer. I wrote a module with Functors, Compose, Partial etc.
In code review my team told they didn't understand anything and reading such a code is not pleasant for them. I was upset, I was thinking why my team is not happy with it.
Today I can make lot of sense; stick to the pattern/design of your team. If your team follows / loves functional programming in your project, stick to it. If not try to advice them why functional programming would be better than normal approach.
End of the day, all it matters is writing simple, elegant code which others can understand.
With the exception of shell scripting which I'm writing every day, I remove "code" more than I write it. C is the language I deal with.
The easier it is to remove code from someone else's program, the "cleaner" the code.
That's my definition of "clean code".
For example, I just had to edit the code for a text-only browser to remove a few "features". For example, the author recently decided it would be a good idea to non-interactively access no-content refs in a page such as "prefetch" and other garbage.
The thing I learned from working with absolute beginners to mediocre developers is: good modularization/isolation is the key to success.
Yes, most things in the article are nice to have, but you can't teach everything in a month and people need to be prodcutive to be worth their money.
A piece of code should not know a lot about the system and should work under a restricted set of assumptions and preconditions.
When this rule is not respected:
1) You end up with lot of coupling
2) Unit tests end up with a lot of overhead
in the background image, the code would be better with arrow functions, or object shorthand methods
A "Top X" post on HN. And can there really be anything new said about qualities of clean code now that hasn't already been said many ... many times before? Given that it is a "Top X" post, I don't feel confident there will be.
I'm passing on this article.
Over the years I've seen many more unmaintainable atrocities caused by diligent adherence to these principles than I ever have because of ignorance of them.
All the horrid Java libraries that we hated working with 10 years ago were created because of slavish devotion to the single responsibility principle, short methods, DRY, and testability. The absolute worst codebases I've ever seen sprung from an overaggressive concern for extensibility.
I do agree with the guiding principle, that when you're writing code, it should be written for other people to read and work with. But sometimes that actually does mean that a class should represent an object that does more than one thing, and you shouldn't fight it. Sometimes a gritty algorithm really does make the most sense if it's laid out as a 40 line function in one place rather than spread across 5 different classes so each part is independently swappable and testable. Sometimes you really don't need extensibility, you know that up-front, and building it in just pisses off everyone that ever has to touch your code because now they have to go through five or six "Find implementing classes" dances to find the actual code that runs when your interface methods (of which, of course, there's only a single implementation) are called. Don't even get me started on abuses of dependency injection when it's not necessary...
Part of me is glad that these concepts are so commonly discussed, because they really are good things to consider, and can make code much tidier and easier to work with in the best case. But it takes a lot of experience to know when and where you should and shouldn't follow these "rules", and it tends to be much easier to unwind a novice's spaghetti code and tidy it up than it is to pick apart a poorly conceived tangle of abstraction and put it back together in a way that makes sense.