Making code better with code reviews
blog.cloudflare.com
blog.cloudflare.com
if (version < 8 && ua ~ "IE") ...
is better written as IE8 = (version < 8 && ua ~ "IE");
if (IE8) ...
This allows variable IE8 to be (1) reused later on in the code, (2) is self documenting and (3) sets a precedent for extending it easily by future devs: IE8 = (version < 8 && ua ~= "IE");
mobile = (ua ~= "mobile")
if (IE8 || mobile) ...
Another quick one is Guard Clauses[2]. function {
if (...) {
...
} else {
...
}
}
where either block is long, is better written as function {
if (...) {
return ...;
}
...
}
This site http://sourcemaking.com/refactoring is worth a read even if you have been programming for years; especially if you have been programming for years and haven't developed good habits. [1] http://sourcemaking.com/refactoring/introduce-explaining-variable
[2] http://sourcemaking.com/refactoring/replace-nested-conditional-with-guard-clausesIt's not something I'd previously considered doing as it seems a bit unnatural at first, but reducing nesting does improve readability over dogmatically sticking to "single return point".
However, I'm not convinced that void functions are better written in the guard-clause style. I think the intent of the code is better expressed with nested ifs. Of course, try to factor the ifs so that they're readable.
I'm saying this is more readable if the "do something part" is less than about 15 lines. Thoughts?
void do_something(input) {
if (!input.already_done()) {
// do something.
}
}Having a big blob of comments above the code, to me is bad. It might have been good, but I'm adverse to "ASCII art"
I think the change they did went further in the direction of explaining it then the comment blob there
Also, doing things in the X or Y way, when it's a matter of opinion/readability (readable/easy to understand code may not be the cleanest/most concise)
And PLEASE don't do "consts everywhere", this:
total_seconds = hours * 3600; // seconds per hour
is better than
total_seconda = hours * SECONDS_PER_HOUR;
No, the number of seconds per hour is not going to change.
The next programmer doesn't need to see those digits at all, they're nothing but a distraction.
That's been my experience. I've yet to work on or with a team that has been able to resolve the concept of a consistent coding standard.
newTime = currentMillis + 21600000,
I go WTF is that number and is it right,
newTime = currentMillis + SIX_HOURS_IN_MILLIS
and I not only know at a glance what you're doing, I know what the value ought to be, so if you accidentally added/left out a zero, I can actually check it where that const is declared.
newTime = currentMillis + 6 * HOUR_IN_MILLIS
But an even cleaner solution would be to just use the date/time library for this (depending on the language, of course): newTime = currentMillis + TimeSpan.FromHours(6).Milliseconds;More importantly: Why 6 hours?
The comment by bluefinity gets it right. HOURS_IN_MILLISECONDS is ok, but don't "constant" everything.
newTime := currentMillis + 6 hours asMilliseconds.
Yes, so if it's only one case you can have the comment
Now, if you're doing it a lot of times I bet your problem is bigger than just adding a constant, it may be even better to turn this into a function so you do to_seconds(time_in_hours) or something
And by using a constant you're making sure that it's not going to change accidentally (typo in one of 17 locations).
Personally, when I review code I never look at it from an X or Y way, but from a current project way. I do not agree with how my current project does everything, but consistency is more important in a large code base until I can schedule time to change things I do not agree with across the entire code base.
To your specific case, someone who uses 3600 instead of making a constant probably has other magic numbers scattered about.
Also, remember the code that exists needs to serve as a guide for future programmers. The next programmer who comes in and sees 3600 now may think magic numbers are okay. So the minor thing you called a nitpick is picked up by someone else and made more of an issue, and so on.
One nice write-up on readability I saw is this: http://www.perlmonks.org/?node_id=592616 along with sources cited therein. It sums up the problems with defining readability nicely.
The bottom line is that we can't say for sure if this:
total_seconds = hours * 3600; // seconds per hour
is any better than this: total_seconds = hours * SECONDS_PER_HOUR;
or this: total_seconds = hours * 60 * 60;
or this: total_time = hours * 60 * 60; // in seconds
or this: total_time = TO_SECONDS( hours );
or this: total_time = SECONDS_FROM_HOURS( hours );
or... you get the idea.And we not only don't know which is better in general, but even which would be better in most specific conditions. We just don't know. Saying otherwise is just an expression of opinion or personal preference. I think acknowledging this would be a decent first step towards improving the readability level of code in general.
A lot on readability stands on the expectations of the reader (and the idioms of the language) as well. For a lisp programmer (* (hours 3600)) may be easier to read for example.
Now I can take that example and turn into
ttscds = x * (36 * 10 * 10);
And we know this is less readable, the intentions are hidden and nobody unfamiliar with the code knows what's going on.
total_seconds = hours * atoi(getenv("SECONDS_PER_HOUR"));
Because you know, we might want to change that value some day.I've got a blog post coming up along these lines in the next few days.
And someone is going to criticize you saying SECONDS_PER_HOUR may be a float, so you should use atof
total_seconds = hours * 3500; // seconds per hour
the compiler will happily let me do this and I may or may not notice the error. If I write
total_seconds = hours * SECOND_PER_HOUR;
the compiler will complain and I'll fix the typo.
// raw[0:1] ID of the query
// raw[2]
// bit 0 - QR
// bit 1:4 - OPCODE
etc
Easier to write and possibly to read.
Take a look at this RFC defining websockets written in 2011
http://tools.ietf.org/html/rfc6455#section-5.2
and the RFC for TCP from 1981 (check out section 3.1 describing the header)
http://www.ietf.org/rfc/rfc793.txt
See the similarity? We have over 30 years of consistency here.
See http://www.ietf.org/rfc/rfc1035.txt Section 2.3.2
I have worked in a few companies and such behaviour almost never happened.
The management always wanted to maintain the old stuff and only resorted to a rewrite, when things were really really bad. "We don't have the time/money" was always the reply. But I had the impression, that a rewrite wasn't that expensive, it always went faster than the first version, without many bugs the first version had and a better structure.
Eventually, it's just better to start again.
For example, right now I'm rewriting something that is currently a PHP program that executes a Python program that runs one of three binaries or calls a REST API and posts results back to another API into a single program.
Just makes sense: easier for developers to maintain, better for SRE to manage when deployed.
The best orgs do a rewrite usually in the face of sea changes to the technology stack (e.g., containers vs. VMs) or business use (number of users is now 10X of original etc). Bringing key infrastructure in-house is also a decent reason, but the benefit of building expertise in an existing ecosystem vs. full rewrite should be carefully weighed.
You probably have read spolsky's article. It's quite dated by now, but it's still more right than wrong. http://www.joelonsoftware.com/articles/fog0000000069.html
When I start a rewrite I check which are the essential features and try to iterate my way to 100% (sometimes the 100% aren't even needed) with many small releases, but every executive fears "rewrites" like the devil....
But I have the feeling, that a prio-list of the needed features is all that is needed.
People say what they need, you implement it, done.
I mean the way other companies steal your customers IS that they implement a better version of stuff you did. So it's either, they do the rewrite (in their case a first write) or you.
Far too often, code grows stale because nobody wants to work on it anymore. Every change is stressful because it could break something else, which means more time wading through unfamiliar and unpleasant code. So, the maintenance programmer patches it with silly putty and duct tape as to not bother any other portion of the system.
A few years of this and it becomes brittle.
If we have a decent suite of tests, however, we can have an application that doesn't need to be rewritten because the maintenance programmer can attack a problem with the confidence that they haven't broken anything else, and so can make the right solution rather than the least invasive solution.
return !(raw[2]&(QR|AA|TC) == 0 && raw[3]&(Z|RCODE) == 0 )