Skip to main content

Write Readable & Consistent Functions

· 4 min read

Motivation

I try to watch coding-related conference talks once in a while and thought that my recent pick of Design Strategies for JavaScript API by Ariya resonate with me. Here's a summary and discussion on the topic of code quality based on ideas from the talk.


Code Quality

While the talk focuses on API design, it speaks to all programmers as writing functions that are used across classes, modules, and files is a common task. What's worse than inconveniencing others is the fact that some functions are misleading even to the author. When we do write functions, we should strive to achieve the following:

  • Readable
  • Consistent

Readability

Read Out Loud

If you can't pronounce or easily spell out the function name, it deserves a better name.

Avoid Boolean Traps

Often the first toolkit that we get hold of when we start to modify a function to meet the new requirements is "Boolean parameter". We add a true/false value at the end of the existing parameter list. It won't be long before our list of parameters grows out of control and we can't pinpoint which parameter is responsible for what anymore.

One potential fix is to use an option object:

person.turn("left", true) // turn left and take one step forward
person.turn("left", false) // turn left and stay at the same place
// change to
person.turn("left", {"stepForward": true})
person.turn("left", {"stepForward": false})

Another refactoring idea is to abstract out the commonly used function into a separate function, so perhaps:

person.turn("left", true) // turn left and take one step forward
person.turn("left", false) // turn left and stay at the same place
// change to
person.turnAndStepForward("left") // if this combination is often used

Do not jump into abstractions too quickly though.

Use a Positive Tone

This might appear to be a glass-half-full or glass-half-empty subjectivity point of view. However, the talk gave by Ariya suggests that we should avoid double negatives such as x.setDisabled(true) and use x.setEnabled(true) instead. This is to help with understanding statements more intuitively. It is also important to use one over the other consistently.

Explicit Immutability

I think this is one of the main takeaways I gathered from the talk. While I try my best to write immutable functions, some level of mutability is hard to avoid. When we do have functions that can either be mutable or immutable, it might be beneficial to indicate that in the function name. For example:

aString.trim() // modify the existing string
aString.trimmed() // only return a modified string

Consistency

Naming

To be consistent is to be predictable. This relies on making smart observations about the existing norm and agreed-upon conventions. With the knowledge of what we believe all programmers should know, which can be patterns and structures that are familiar, best-practices, or stood the test of time, we can write functions that will turn out to be unsurprising to potential readers.

On a smaller scale, if two functions do similar things, they ought to be named similarly. This is an extension of the idea of polymorphism. For example:

person.turn("left")
car.steer("left")

Perhaps a better way to name the functions will be to use turn for both.

person.turn("left")
car.turn("left")

Parameters

In the same vein, having consistent parameters will help to reduce mistakes. For example:

person.rotate(1, 2) // first horizontally, second vertically
rectangle.rotate(1, 2) // first vertically, second horizontally

Suppose that both objects have a method called rotate but the parameters are two different ordered pairs of the same values. That is a disaster in the making.


Conclusion

With the help of powerful IDEs, we now enjoy the convenience of having documentation of functions available as we write code. This may make recognizing what a function is doing or what each parameter means easier, but it should not be an encouragement to write bad functions. Also, if someone is already making a mess writing code, it may not be wise to trust his/her documentations, if there is any...

Python Code Style Fix Notes

· 4 min read

Motivation

I am working on fixing some of the issues raised by flake8 (a Python Linter) in a Python-based backend repository and thought it would be nice to discuss some of the common issues and the solutions that I gathered from the web (well, mostly StackOverflow). The use of an auto formatter such as black will help resolve some of these common issues automatically. flake8rules is an excellent resource of a complete list of issues as well.


line too long (92 > 79 characters)flake8(E501)

Line too long issues mainly happen for the following cases:

  • string
  • if-statement
  • method chaining
  • parameter list

... I was going to explain with examples how to use Python's implied line continuation inside parentheses, brackets and braces but decided not to. Nowadays I chose to leave it to my auto formatter to do the job.

For those who insist to write code without any helpful plugins or IDE support, I would like to share that practice does make perfect. I used Vim for a period of writing Java code without autocomplete or format-on-save. I ran style checks and manually fixed issues raised such as having a space between operators. After a month or two, these things became second nature and I was pretty happy with the ability to write well-formatted code without any help. I suppose that was an interesting experience so go ahead and try it yourself.


do not use bare 'except'flake8(E722)

Example:

def create(item):
try:
item("creating")
except:
pass

Fix:

def create(item):
try:
item("creating")
except Exception:
pass

Explanation:

Bare except will catch exceptions you almost certainly don't want to catch, including KeyboardInterrupt (the user hitting Ctrl+C) and Python-raised errors like SystemExit

A better fix:

  • Think about what exact exception to catch and specify that instead of just catching any exception.
  • Think about whether this exception handling is necessary and are you unintentionally using it for control flow?
  • When catching an exception, use either logging or other proper resolutions to handle the anticipated error.

'from some_package_name_here import *' used; unable to detect undefined names flake8(F403)

I thought this is an interesting topic for discussion. Earlier in my coding journey, I was amused by the many lines of import statements found in some scripts. Sometimes the number of import statements outweigh the number of practical code within the same file.

Nowadays I know better than to fear abstractions (I still fear BAD abstractions and rightfully so). However, with the help of powerful IDEs, importing and tracing the variable/function to their imported package is easier than before. The problem with 'from xxx import *' is that it is unclear what has been imported. Following this, IDEs could not decide whether some of the undefined variables come from the package that you imported or they are errors.

Example

from package_a import *
from package_b import *
# suppose both packages included a function named pretty_print
# it is unclear which method is invoked below
pretty_print("abc")

Fix

from package_a import pretty_print
from package_b import other_function_required
pretty_print("abc")

Conclusion

When browsing an unfamiliar code repository, we tend to have less sentimental feelings and that fresh perspective allows us to see the good, the bad, and the evil. Besides learning from the good practices, the hacky and the code standard violations (and things like commented out code) are excellent places to start reviewing concepts of coding styles and coding standards, to find out why code misbehaved.

That's all for now.


External resources:

Updating State With useState Hook

· 5 min read

Motivation

The useState hook is a convenient method to provide a temporary component state. It is very common to invoke the useState hook for added interactivity of a button or other visual components. There are 3 main concerns when using the useState hook.

  • Is the state necessary?
  • Is the state located at the right level in the component tree?
  • Is the state updated correctly?

The first 2 questions can be answered with some considerations on the choice of state management solutions and whether to use composition vs inheritance. They are somewhat discussed here in Composition vs Inheritance , Lifting State up and Thinking in React. I would like to share a little bit on updating the state properly (which I failed to do so in a new feature that I was implementing).

Encounter

I was adding a search + sort feature to display a list of quizzes in my side project done in Next.js (which is practically React). To keep track of the search input and the sorting option, I used two useState hooks. I have extracted out the related piece of code below, style related classes and components are left out for brevity.

In gist :

  • QuizList component receives a list of quizzes
  • query keeps track of the search input
  • sortBy keeps track of the sorting option
  • filteredQuizzes keeps track of the manipulated copy of quizzes to be displayed
const QuizList = ({ quizzes }: { quizzes: Quiz[] }): JSX.Element => {
const [query, setQuery] = useState('')
const [sortBy, setSortBy] = useState('title')
const [filteredQuizzes, setFilteredQuizzes] = useState<Quiz[]>([])
useEffect(() => {
let result = quizzes
if (sortBy === 'title') {
result.sort((a, b) => a.title.localeCompare(b.title))
} else {
result.sort((a, b) => a.week - b.week)
}
if (query.trim() === '') {
setFilteredQuizzes(result)
} else {
setFilteredQuizzes(
result.filter((quiz) => quiz.title.toLowerCase().includes(query.toLowerCase()))
)
}
}
}, [query, quizzes, sortBy])
return (
<div>
<Search query={query} setQuery={setQuery} />
<RadioGroup onChange={setSortBy} value={sortBy}>
<Stack direction="row">
<span>Sort by:</span>
<Radio value="title">Title</Radio>
<Radio value="week">Week</Radio>
</Stack>
</RadioGroup>
<div>
{filteredQuizzes.map((quiz) => {
return <QuizItemCard key={quiz.id} quiz={quiz} />
})}
</div>
</div>
)

This is how it looks like: before

I must say that the few times I decided to violate the principle of immutability and start my code by declaring variables with let instead of const, they always surprised me with a hidden bug. The above code seemed to work but there was a strange lag when I toggle between the options for sorting by 'Title' vs sorting by 'Week'. In fact, the sorting seemed to be erroneous.

The logic of the code is as follows:

  • check the sorting option, if it is sort by title, sort the list of quizzes in place with a comparison on the titles. Else, sort the list by the week attribute of each quiz
  • then check for search input and keep only the ones that include the search input

I suspected that the inconsistent and delayed sorting behavior was due to the mutation of quiz list in place and the wrong use of setFilteredQuizzes. Coincidentally, the article that I planned to write this week was related to the official React.js FAQ and reading its section on Component State gave me an idea on how to fix the state update.

Fix

As mentioned in the Component State section of the FAQ, setState operations are not immediately invoked and inappropriate usage will result in unintended consequences. Quoting an example code snippet directly from the document:

incrementCount() {
this.setState((state) => {
// Important: read `state` instead of `this.state` when updating.
return {count: state.count + 1}
});
}

handleSomething() {
// Let's say `this.state.count` starts at 0.
this.incrementCount();
this.incrementCount();
this.incrementCount();

// If you read `this.state.count` now, it would still be 0.
// But when React re-renders the component, it will be 3.
}

Because of the above fact, it helps to know that the setState operation can accept either the updated state or a update function that will take in the previous state and return the updated state.

Thus my solution is simple: use the spread operator to make a copy of the list, sort it and return it within an update function. By making updates within the update functions, the latest updated list will be used whenever filteredQuizzes is referenced. Other alternative solutions include updating the filteredQuizzes in event handlers of the sorting radio buttons instead of keeping track of the sorting state.

The fixed version looks like this: after

And the code as follows:

const QuizList = ({ quizzes }: { quizzes: Quiz[] }): JSX.Element => {
const [query, setQuery] = useState('')
const [sortBy, setSortBy] = useState('title')
const [filteredQuizzes, setFilteredQuizzes] = useState<Quiz[]>([])
useEffect(() => {
if (sortBy === 'title') {
setFilteredQuizzes(() => [...quizzes].sort((a, b) => a.title.localeCompare(b.title)))
} else {
setFilteredQuizzes(() => [...quizzes].sort((a, b) => a.week - b.week))
)
}
if (query.trim() === '') {
setFilteredQuizzes((filteredQuizzes) => filteredQuizzes)
} else {
setFilteredQuizzes((filteredQuizzes) =>
filteredQuizzes.filter((quiz) => quiz.title.toLowerCase().includes(query.toLowerCase()))
)
}
}, [query, quizzes, sortBy])
return (
<div>
<Search query={query} setQuery={setQuery} />
<RadioGroup onChange={setSortBy} value={sortBy}>
<Stack direction="row">
<span>Sort by:</span>
<Radio value="title">Title</Radio>
<Radio value="week">Week</Radio>
</Stack>
</RadioGroup>
<div>
{filteredQuizzes.map((quiz) => {
return <QuizItemCard key={quiz.id} quiz={quiz} />
})}
</div>
</div>
)

Conclusion

As someone who is guilty of being lazy, my initial response to the above bug after some experimentation was to remove the sorting option and just sort the quizzes coming in. Due to the fact that I set out to write this article, and that I was inspired by Kent C. Dodds to read the React FAQ, I avoided taking the easy way out and spent some more time thinking about this little problem.

I guess the moral of the story is:

  • Don't always take the easy way out
  • Give problems a second thought
  • Start writing articles