Table of Contents
Effective Programming Techniques
I've learned these techniques over time, and I'd like to share them with you.
My 5-Step Coding Procedure
I follow a simple, five-step procedure for every new function I write:
- Write: First, I implement the core idea to get it working. The initial goal is just functionality, a clever thing is to keep in mind the core structure it should have in the future.
- Refine: Next, I improve the code's structure, ensure correct return values, and format it for readability.
- Clarify: I choose descriptive names for variables and functions. This step can be slow, but it's one of the most valuable programming practices.
- Document: I add comments where necessary to make the code easy to understand.
- Simplify: Finally, I remove unnecessary code and simplify algorithms. Reviewing the function multiple times at this stage often reveals unexpected bugs.
Managing States
Managing states helps your code know what to do in various situations. A simple way to do this is to use boolean flags liberally—they are inexpensive and help your code track its status and decide how to proceed.
To avoid a bloated codebase with confusing flags, organize them well. For example:
- The prefix is_ is excellent for boolean states (e.g., is_sources_get, is_sources_compiled, is_interactive). As your application performs actions, you can set or unset these variables to define its state and progress.
- For different modes of operation, use prefixes like mode_ (e.g., mode_interactive, mode_editing).
- Another useful prefix is ignore_ to skip certain steps.
Anticipate expected situations. While you can't foresee every possibility, consider edge cases like, "What if a file is deleted while in use?" or "What if the user performs another action simultaneously?"
Handling Special Situations
While states and flags are useful, it's even better if you can avoid the need for them. Before implementing complex logic for a special case, consider if there's a simpler way to handle it, or to avoid it entirely. Always strive for simplicity, always avoid complex coding.
Deferring Actions
Applications often perform a series of steps. For instance, a process might perform some actions and then save the result to a database. However, you should only save to the database if the entire process completes successfully.
One way to manage this is to use a flag to indicate that a final action is needed. When the process finishes successfully, a function like final_steps can execute all the deferred actions. This is useful for tasks like:
- Removing temporary directories
- Committing database transactions
- Sending notifications
- Unmounting drives or closing shared resources
The Importance of Checks
Your code will never be perfect. Don't assume everything will work as expected. Unexpected failures can lead to catastrophic results. To prevent bugs and disasters, follow a simple rule: check everything.
Create short, dedicated functions for checks. For example, you could have check_directories
, check_variables
, and check_files
. Using a consistent naming convention, like the check_
prefix, groups these utility functions together logically.
Human factors
Memory
You won't remember exactly what a piece of code does or why it's there weeks or months later. While comments help, they can be forgotten or become outdated.
Always keep your comments updated, now we have tools that automates this task.
Visual Readability
Readability is crucial and extends beyond good naming. The visual layout of your code matters. For example:
- If an else is many lines away from its if, it can be hard to follow. Consider closing the first
if
and starting a new one instead of usingelse
. - Align similar elements into vertical columns to make blocks of code easier to scan.
- Whitespace greatly improves readability. Use it generously. Combined with code folding in your editor, you can keep your code clean without losing sight of its structure.
Visual Blocks
Visual blocks of code should be easy to identify. For example, in a large loop, it can be hard to see whether you are inside or outside of it, which can lead to bugs. Use editor features like code folding to collapse large blocks, or extract them into separate functions.
Similarly, if
statements and other control structures should be visually distinct so you can immediately see where they start and end.
Add functions to long parts of the code inside the loops, but only when the loop is not an optimization-required one.
Think Less
Code that requires less mental effort is faster to read and understand. For example:
- Prefer
count < 3
overcount <= 2
. - Avoid negatives; they are harder to reason about than affirmatives. For example:
if not excluded
can becomeif included
.if is not disabled
is better asif enabled
.- Even
ignore
can be a negative. Consider a positive form likeif include_item
instead ofif not ignore_item
.
- Write short, clear sentences in comments and documentation.
Naming Conventions
Variable and function names are very important. A good convention is to make names as short as possible while remaining perfectly understandable. Think of a hierarchical structure, like in object-oriented programming, where names go from general to specific, often ending with an action.
It is good practice to review the final code, brainstorm their names, and rename variables, functions and global variables once a major part of the application is complete. This helps ensure a consistent and optimal naming structure throughout the codebase.
Poor Naming Examples
- download_resource_now: The action is first,
resource
is too generic, andnow
is redundant. - stmb_sizer_here_here: Prefixes like
stmb
are unclear without context,sizer
is ambiguous, and repeated words likehere
signal confusion.
Good Naming Example
- cookie_download: This is better. It's clear we are dealing with cookies, and
download
is the action.
Optimal Naming Example
- myplugin_net_cookie_download: A fully structured name is often best. Here,
myplugin
is the project/module,net
is the component (e.g., core, net, UI, audio),cookie
is the object, anddownload
is the action.
Often, good function and variable names seem to be in the reverse order of how you would say them. If in doubt, imagine a list of similar names sorted alphabetically. This can help you see which parts of the name should come first.
It's also helpful to know a variable's type from its name. Some conventions use suffixes for this, such as _v
for values, _a
for arrays, _d
for directories, and _f
for files (a form of Hungarian notation). Use a _
prefix for unimporant local variables.
Big Applications
Large applications are difficult to maintain and debug. The best way to avoid these pains is to:
- Keep everything modular and independent. Every function, tool, or feature should be callable independently without relying on other parts of the application. Consider using external commands to achieve this.
- When calling external commands or global tools, include a debug entry entry of the call with its arguments. This makes it easy to reproduce the call outside the application for debugging.
- Avoid deleting or renaming old functions that other tools might depend on. Instead, mark them as deprecated, have it to call the new function with the updated parameters, and add a warning message to inform developers of the change.
- When refactoring a function, do not change the final behavior. This can cause unexpected results in applications that use the function.
Remember
- Be as descriptive as possible.
- Don't use generic names.
- Don't use negative logic in variable names.
- Be consistent with the other elements.
- Map your names to the application's domain terminology.
Golden rule: Spend a few minutes thinking about your variable names.
Wisdom
- The secret to building powerful apps is to never build large apps. Break your applications into small, testable pieces. Then, assemble those pieces into your larger application.
- Reducing the amount of code in your product should be a goal.
- If you are changing more than 25% of a class or method, consider rewriting it. You will likely write it more cleanly.
- Accept that you don't know how your application will grow. This leads to a defensive design. Spend more time on interfaces than implementations.
- Write functions that are self-contained and don't rely on global variables. This makes them easier to test and reuse.
Why Rewriting is (Almost) Never a Good Idea
- It always takes longer than you expect.
- Existing users become frustrated.
- Refactoring changes your code and behaviour.
- You don't control the rewrite; it controls you.
The Zen of Python
- Beautiful is better than ugly.
- Explicit is better than implicit.
- Simple is better than complex.
- Complex is better than complicated.
- Flat is better than nested.
- Sparse is better than dense.
- Readability counts.
- Special cases aren't special enough to break the rules.
- Although practicality beats purity.
- Errors should never pass silently.
- Unless explicitly silenced.
- In the face of ambiguity, refuse the temptation to guess.
- There should be one-- and preferably only one --obvious way to do it.
- Although that way may not be obvious at first unless you're Dutch.
- Now is better than never.
- Although never is often better than *right* now.
- If the implementation is hard to explain, it's a bad idea.
- If the implementation is easy to explain, it may be a good idea.
- Namespaces are one honking great idea -- let's do more of those!
Art of Unix programming
- Rule of Modularity: Write simple parts connected by clean interfaces.
- Rule of Clarity: Clarity is better than cleverness.
- Rule of Composition: Design programs to be connected to other programs.
- Rule of Separation: Separate policy from mechanism; separate interfaces from engines.
- Rule of Simplicity: Design for simplicity; add complexity only where you must.
- Rule of Parsimony: Write a big program only when it is clear by demonstration that nothing else will do.
- Rule of Transparency: Design for visibility to make inspection and debugging easier.
- Rule of Robustness: Robustness is the child of transparency and simplicity.
- Rule of Representation: Fold knowledge into data so program logic can be stupid and robust.
- Rule of Least Surprise: In interface design, always do the least surprising thing.
- Rule of Silence: When a program has nothing surprising to say, it should say nothing.
- Rule of Repair: When you must fail, fail noisily and as soon as possible.
- Rule of Economy: Programmer time is expensive; conserve it in preference to machine time.
- Rule of Generation: Avoid hand-hacking; write programs to write programs when you can.
- Rule of Optimization: Prototype before polishing. Get it working before you optimize it.
- Rule of Diversity: Distrust all claims for “one true way”.
- Rule of Extensibility: Design for the future, because it will be here sooner than you think.
Extra Tips
- Always use local variables. Avoid sharing them externally to prevent unexpected side effects.
- In functions longer than ~15 lines, avoid reassigning local variables. Use a new variable instead for clarity.
- Use a powerful code editor with good plugins (e.g., Vim with a good configuration).
- Trap signals to handle exit conditions and errors gracefully.
- The simplest solution is usually the best. Don't over-engineer features that aren't needed.
- Design for concurrency. If you use a temporary directory like
/tmp/myapp
, you'll have conflicts if multiple users or processes run the application simultaneously. Use user-specific or process-ID-specific temporary directories (e.g.,/tmp/myapp-<user>
or/tmp/myapp-<pid>
).
- 10 Rules for Creating Good Code - that must be read until becomes subconscious