Good things I sometimes forgot when coding
After coding for a while, I now find myself, from time to time, caught in situations where I wrote some bad(-style) code because I forgot the good. The reasons vary but this one stands out: I did not really take a concept or definition seriously at the very beginning and did not make it a habit. This post is therefore a note for now and future me.
if
statement vs if
expression block
In Python's term, if
is a statement instead of expression. In Python's if
blocks, rarely did I even think about returning a value or result.
In Rust, however, since if
is followed by a block
, it can also act as expressions, which means it is fairly common in Rust to return a value from a if
block:
// Common practice
let x = if a != 10 { a } else { a + 1 };
// Instead of this
let mut x = 0;
if a != 10 {
x = a;
} else {
x = a + 1;
}
Or in Haskell, the mandatory else
branch requires something to be returned:
doubleSmallNumber x = (if x = 10 then x else x*2) + 1
Or in Clojure:
(if (= a 10)
a
(* a 2))
Boolean expressions
My die-hard habit is the following:
if x != 10 {
correct = false;
} else {
correct = true;
}
But why not simply this:
correct = x != 10;
In many languages, when an expression evaluates to a value, I should use the expression's return value, rather than merely evaluate it.
.length
v.s. len()
It's normal to loop over an array using something like
for (int i=0; i < arr.length; i++) // do sth here
In Java the length
property of an array is static and determined at compile time. That means accessing its value is of O(1)
time complexity.
Yet for dynamical arrays (a.k.a vectors in some other languages), using len()
method has some overhead. Thus, it is good to store the length in a variable and use it thereafter (assuming the /length/ doesn't change, of course)
int N = someArr.len();
for (int i=0; i<N; i++) // do sth here
Indicating possible failure in initialization
In Rust, when initializing an instance of my own types (usually struct
), I can use different function names to indicate different results
struct Student {
name: &str,
age: u8,
}
// For sake of simplicity, I ignore lifecycle of &str here
impl Student {
fn new(name: &str, age: u8) -> Self {
Self {name, age}
}
// Indicate it may fail to initialize a new student
fn try_new(name &str, age: u8) -> Result<Self, MyError> {
Ok(Self {name, age})
}
}
Think about the "forest" all time
When coding, it's easy for me to get obsessed with specific implementations/details/steps only to find I wasted much time later ... It's good to always keep the whole picture in mind. Thus instead of rushing into coding, I should make good plans:
int main()
{
FirstThing();
SecondThing();
}
Then work on FirstThing()
, and then SecondThing()
. A basic plan always helps.
Do one thing at a time
Sometimes it is easy to write some hot code that gets lots of things tangles in one place. For example, in a function several variables or states get updated based on different branches. This often indicates a good time to refactor the code.
To be continued ...