Beautiful code
Some days ago, my friend requested me to help him review his code. Well, it was my daily job, so, I pleased to help.
Disclaim: Sample code is written in TypeScript, however, I’m not familiar with the language. So, correct me if I’m wrong.
Here is the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function getSrcTokensAndDestTokens(network) {
const tokenInputs = this.srcTokens.concat(this.destTokens);
for (const token of tokenInputs) {
if (network.tokens[token].hidden || network.tokens[token].delisted) {
console.log(`GSI doesn't support ${token} yet!`);
return;
}
}
if (this.srcTokens.length === 0 && this.destTokens.length === 0) {
console.error("srcTokens and destTokens can't be both empty!");
return;
}
if (this.srcTokens.length === 0) {
for (const token in network.tokens) {
if (!network.tokens[token].hidden && !network.tokens[token].delisted) {
this.srcTokens.push(token);
}
}
}
if (this.destTokens.length === 0) {
for (const token in network.tokens) {
if (!network.tokens[token].hidden && !network.tokens[token].delisted) {
this.destTokens.push(token);
}
}
}
// ...
return {
// ...
}
}
What’s wrong with this code?
This method implementation is long, let’s break it aparts first, so, we can talk on each piece of the function.
1
function getSrcTokensAndDestTokens(network)
What is src
dest
? I guess they are source
and destination
respectively. But we shouldn’t do that, we shouldn’t guess. Or, we shouldn’t make readers guess what did we want to say. So, let’s change the function name
function getSourceTokensAndDestinationTokens(...)
or
function getSourceAndDestinationTokens(...)
(Update: if you feel Destination
long, we can use Target
)
Next, network
may be okay, however, it’s too common. Is it the internet network? or LAN network? or personal network? or Umbala Network? In this case, we should give it a more specified name. Maybe gsiNetwork
.
2
const tokenInputs = this.srcTokens.concat(this.destTokens);
Yes, we should use source
and destination
.
3
for (const token of tokenInputs) {
if (network.tokens[token].hidden ||
network.tokens[token].delisted) {
console.log(`GSI doesn't support ${token} yet!`);
return;
}
}
Mmm… we have a for-loop
and an if
for checking error here. getSrcTokensAndDestTokens
is a caller, we should simplify the error check within only an if for that purpose. Something like
if (!isValidTokenInputs) {
return
}
Before refactoring, let’s take a look into the loop’s body. network.tokens[token]
is called twice. Besides, hidden
and delisted
are not a good name for boolean values. Let’s refactor
const token = network.tokens[tokenInput] // #1
if (token.isHidden || token.isDelisted) {
// ...
}
// Note:
// #1: we should use a similar name to the loop iterator, here is
// `tokenInputs`, therefore, we should name `tokenInput` instead of
// `token`
Next
console.log(`GSI doesn't support ${token} yet!`);
This log is hard to search when we have tons of logs. Why? Because ${token}
is in the middle. The output log looks like:
GSI doesn't support KJHKJKY4edf5ljbjdbLLBLHJBERB yet!
GSI doesn't support ljdilhKHKJBJHJGVKUYIlJNKJNBH yet!
GSI doesn't support KJHJK yet!
I also think it’s not grammar correct (maybe I’m wrong). We shouldn’t hardcode too. It’s should be
const ERROR_LOG_NOT_SUPPORTED_TOKEN = "Token not supported" // #1
// ...
console.log(ERROR_LOG_NOT_SUPPORTED_TOKEN, token)
// Note:
// #1: We may add ERROR CODE
Okay, let’s make the for-loop
fancier
const ERROR_LOG_NOT_SUPPORTED_TOKEN = "Token not supported"
function isValidTokenInputs(network, tokenInputs) {
for (const tokenInput in tokenInputs) {
const token = network.tokens[tokenInput]
if (token.isHidden || token.isDelisted) {
return [false, tokenInput] // We should create a type instead of using an array or
// a pair because it looses context information
}
}
return [true, ""]
}
function getSrcTokensAndDestTokens(network) {
// ...
const [isSourceTokenValid, wrongSourceTokenInput] =
isValidTokenInputs(network, tokenInputs)
if (!isSourceTokenValid) {
console.log(ERROR_LOG_NOT_SUPPORTED_TOKEN, wrongSourceTokenInput)
return
}
//...
}
Isn’t it easier to read? I hope so
4
if (this.srcTokens.length === 0 && this.destTokens.length === 0) {
console.error("srcTokens and destTokens can't be both empty!");
return;
}
Well, the same for the hardcoded error log. We should have a constant for searching not only inside the log file but also within the code. Name it!
5
if (this.srcTokens.length === 0) {
for (const token in network.tokens) {
if (!network.tokens[token].hidden &&
!network.tokens[token].delisted) {
this.srcTokens.push(token);
}
}
}
if (this.destTokens.length === 0) {
for (const token in network.tokens) {
if (!network.tokens[token].hidden &&
!network.tokens[token].delisted) {
this.destTokens.push(token);
}
}
}
Firstly, these two if-s do the same thing. Secondly, we have depth nested block here. Yeah, for me, 3 is too many. Finally, can you spot these two logics are the same
// #1
network.tokens[token].hidden || network.tokens[token].delisted
// #2
!network.tokens[token].hidden && !network.tokens[token].delisted
I hope you can see it. Here is the discrete mathematics transformation rule
A | B = !!(A | B) = !(!A & !B)
Follow DRY rule, we should define a method to check a token is valid or not
function isTokenValid(token) {
return !token.isHidden && !token.isDelisted
}
The DRY rule also helps use to unify the two if-s into one function. (This writing is long and I’m lazy to type more explanation. Sorry!)
function getValidTokens(tokens) {
const result = []
for (const token in tokens) {
if (isTokenValid(token) {
result.push(token)
}
}
}
And the two if-s will be
const validNetworkTokens = getValidTokens(network.tokens) // #1
if (this.sourceTokens.length === 0) {
this.sourceTokens = validNetworkTokens // #2
}
if (this.destinationTokens.length === 0) {
this.destinationTokens = validNetworkTokens // #2
}
// NOTE:
// #1: We can optimise by checking whether we need to create the
// `validNetworkTokens` with check there is at lest a length === 0
// #2: We may need to clone the `validNetworkTokens` to avoid same
// reference problem
6
It’s your task to combine all of the above code into the final solution :)
Happy coding!!!