introduction
I’m sorry for the clickbait headline, I didn’t have a better idea/name for it.
We (developers) occasionally produce lazy/messy code and from time to time we need to remember the most important rule: “We do code to solve problems but also for human being be able: to use, to maintain and to evolute”.
TLDR; (a unit can be a: function, var, method, class, parameter and etc)
- Naming your units with care and meaning;
- Try to see your code as a series of transformation;
- When possible make yours units generic.
Keep in mind that these tips are just my opinions and at the best they were based on: excellent books (Refactoring, DDD, Clean Coder and etc ), articles & blog posts, excellent people I’ve worked/paired with, presentations, tweets and experiences.
naming is hard
Name your units with care and meaning. Your code should be easy to understand.
Although naming things is really hard, it is also extremely important. Let’s a see a snippet of code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var topComments = (id) => { | |
var succCB = (d) => { | |
var a = d.data.comments | |
var top = [] | |
a.sort((d1, d2) => { | |
return new Date(d1.date) – new Date(d2.date) | |
}) | |
a.forEach((c) => { | |
if (c.isTop()) { | |
top.push(c) | |
} | |
} | |
app.topComments = top.slice(0,10) | |
} | |
var errCB = (e) => { | |
this.sendError(e) | |
app.topComments = [] | |
} | |
this.ajax(`/all/${id}/comments/`, succCB, errCB) | |
} |
Let’s discuss about this code above:
- the function topComments receives an id but is it the id from the comment, user, article? Let’s say it’s form the user, therefore userId should vanish this doubt.
- the name of the function is topComments but it looks like it’s getting the top 10 latest comments only thus we could call it top10LatestCommentsFrom.
- the ajax function accept two callbacks one in case of success (succCB) and otherwise an error (errCB), I believe we can call them: onSuccess and onError for better understanding.
- all the arguments are using short names and we can have less confusing names just by using the entire name.
- you got the ideia, naming things to let the code clear!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var top10LatestCommentsFrom = (userId) => { | |
var onSuccess = (rawData) => { | |
var topLatestComments = [] | |
var allComments = rawData.data.comments | |
allComments = allComments.sort((date1, date2) => { | |
return new Date(date1.date) – new Date(date1.date) | |
}) | |
allComments.forEach((comment) => { | |
if (comment.isTop()) { | |
topLatestComments.push(comment) | |
} | |
} | |
app.topComments = topLatestComments.slice(0,10) | |
} | |
var onError = (error) => { | |
this.sendError(error) | |
app.topComments = [] | |
} | |
this.ajax(`/all/${userId}/comments/`, onSuccess, onError) | |
} |
Although we still have so many problems in this code, now it’s easier to understand and we only named things properly.
For sure there are some cases when short names are just okay, for example: when you’re developing an emulator or virtual machine you often use short names like sp (stack pointer) and pc (program counter) or even doing a very generic unit.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class LR35902 { | |
init() { | |
this.pc = this.sp = 0x0000 | |
this.a = this.b = this.c = this.d = this.e = this.f = this.f = this.h = this.l = 0x00 | |
} | |
execute() { | |
var opCode = memory.read(this.pc) | |
this.perform(opCode) | |
this.pc += 2 | |
} | |
} |
filter -> union -> compact -> kick
Try to see and fit your code as transformations, one after another.
Some say that in computer science almost all the problems can be reduced to only two major problems: sort and count things (plus doing these in a distributed environment), anyway the point is: we usually are coding to make transformation over data.
For instance our function top10LatestCommentsFrom could be summarized in these steps:
- fetch comments (all)
- sort them (by date)
- filter them (only top)
- select the first 10
Which are just transformations over an initial list, we can make our function top10LatestCommentsFrom much better with that mindset.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var onError = (error) => this.sendError(error) | |
var byDate = (date1, date2) => new Date(date1.date) – new Date(date1.date) | |
var onlyTops = (comment) => comment.isTop() | |
var top10LatestComments = (rawData) => { | |
app.topComments = rawData.data.comments | |
.sort(byDate) | |
.filter(onlyTops) | |
.slice(0, 10) | |
} | |
var userId = 68 | |
ajax(`/${userId}`, top10LatestComments, onError) |
By the way this could lead you to easily understand the new kid on the block sometimes referred as Functional Reactive Programming.
<be generic>
Work to make your units generic.
Let’s imagine you are in an interview process and your first task is to code a function which prints the numbers 1, 2 and 3 concatenated with “Hello, “. It should print: “Hello, 1” and then “Hello, 2″…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var printNumbers = () => { | |
[1,2,3].forEach((number) => console.log(`Hello, ${number}`)) | |
} | |
printNumbers() |
Now they ask you to print also the letters: “D”, “K” and “C”.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var print = (list) => { | |
list.forEach((number) => console.log(`Hello, ${number}`)) | |
} | |
print([1,2,3]) | |
print(["D","K","C"]) |
It was the first step toward the “generic”, now the interviewers say you have also to print a list of person’s name but now it’ll be a list of objects [{name: “person”},…].
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var print = (list) => { | |
list.forEach((item) => { | |
// naming become harder 😦 | |
var itemDescription | |
if (typeof item === "object") { | |
itemDescription = item.name | |
} else { | |
itemDescription = item | |
} | |
console.log(`Annyong, ${itemDescription}`) | |
}) | |
} | |
print([1,2,3]) | |
print(["d","k","c"]) | |
print([{name: "Buster Lose Seal"}, {name: "Neo Cortex"}]) |
Things start to get specific again and the interviewers want to test you. They ask you to print a list of car’s brand [{brand: “Ferrari”}, ..] plus a list of game consoles with their architecture [{name: “PS4”, arch: “x86-64”}, …]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var print = (list) => { | |
list.forEach((item) => { | |
var itemDescription | |
if (typeof item === "object") { | |
itemDescription = item.name || item.brand | |
if (item.arch) { | |
itemDescription = `${item.name} – ${item.arch}` | |
} | |
} else { | |
itemDescription = item | |
} | |
console.log(`Hello, ${itemDescription}`) | |
}) | |
} | |
print([1,2,3]) | |
print(["D","K","C"]) | |
print([{name: "Buster Lose Seal"},{name: "Neo Cortex"}]) | |
print([{brand: "Ferrari"}, {brand: "Mercedes"}]) | |
print([{name: "N64", arch: "MIPS"}, {name: "3DS", arch: "ARM9"}]) |
Yikes, I suppose you’re not proud of that code and probably your interviewers will be little concerned about your skills with development, let’s list some of the problems with this approach.
- Naming (we’re calling a person of an item)
- High coupling (the function print knows too much about each printable)
- Lots of (inner) conditionals 😦 it’s really hard to read/maintain/evolute this code
What we can do?! Well, it seems that all we need to do is to iterate through an array and prints an item but each item will require a different way of printing.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var defaultPrint = (item) => console.log(`Hello, ${item}`) | |
var myForEach = (list, printFunction = defaultPrint) => { | |
list.forEach((item) => printFunction(item)) | |
} | |
myForEach([1,2,3]) | |
myForEach(["D","K","C"]) | |
myForEach([{name: "Tomba!"},{name: "Neo Cortex"}], (person) => defaultPrint(person.name)) | |
myForEach([{brand: "Ferrari"}, {brand: "Mercedes"}], (car) => defaultPrint(car.brand)) | |
myForEach([{name: "N64", arch: "MIPS"}, {name: "3DS", arch: "ARM9"}], (console) => defaultPrint(`${item.name} – ${item.arch}`)) | |
// in fact we could even use `Array.prototype.forEach` function and avoid the duplication with `myForEach` | |
// (which does basically what forEach does) | |
// var forEach = Array.prototype.forEach | |
// forEach.call([{name: "Tomba!"},{name: "NeoCortex"}], (person) => defaultPrint(person.name)) |
I said naming is important but when you make something very generic you should also make the abstract names not tied to any concrete concept. In fact, in Haskell (let’s pretend I know Haskell) when a concrete type of something may vary we use single letters to take their place.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function makelist(x) {return [x]} | |
makelist(document.head[0]) | |
makelist("DKC:TF") | |
makelist("6502") | |
makelist(0xf13e455d7a1b96cd2d930e578284d889) |
Bonus round
- Make your units of execution to perform a single task.
- Use dispatch/pattern matching/protocol something instead of conditionals.
- Enforce DRY as much as you can.
You must be logged in to post a comment.