Maximizing the collaborative potential of your software
Pairing with the Future
Kelly Fox
Code is communication
Across time and space
How do developers spend their time?
(Based on how it feels.)
Ease of change directly correlates
with ease of understanding
Rebecca Parsons
CTO, ThoughtWorks
Code is written for humans first,
machines second
Projects are easy to start,
But harder to finish
Cache your context
Suggestions, not rules
What follows are starting points for discussion, and may not be appropriate for every situation.
Some suggestions may be easier to implement than others.
Use these guidelines to help you approach coding with a future-looking perspective. Imagine someone new to your codebase trying to make sense of the code you’re writing.
Project, file, and function structure
Approaches to Project Organization
Follow conventions for the language or framework
Arrange folders intuitively, from abstract to specific
Organize code by features or functionality
Keep related files and code “close together”
General file layout
Imports — built-in, external, internal, each section alphabetized
Variables common to the entire file
Function and class definitions, before use
Main program logic
Exports
Structure of a function or method
Variable declarations and assignments
Early return (if necessary)
Core function logic
Return statement/value
Use blank lines to separate sections or logical chunks of code.
Sizing
Sizing Suggestions
Item
Limit
Line Length
80-120 characters max
Function Arity
3 arguments max
Function Length
4-10 logical lines max
Class
2 pages max
File
3 pages max
Files in a folder
Minimal scrolling
Good functions and methods
int getRandomNumber()
{
return 4; // chosen by 6-sided die roll
// guaranteed to be random
}
Tips for Writing Good Functions and Methods
Functions should focus on accomplishing one task.
Return early from functions when possible to avoid unnecessary processing.
Function layout should be: Variable declarations and assignments, then early returns, then core function logic, then the final return statement. Single blank lines should separate sections for clarity.
More Tips for Writing Good Functions and Methods
Code representing the primary behavior of a function shouldn't be buried inside an if/then statement.
Write short functions. This makes code easier to read, easier to reuse, easier to review, easier to adapt for other purposes, and easier to test.
When possible, avoid: sharing state with other functions, changing state, and side effects.
Arity: The number of function arguments
Try to keep the number of function arguments to three or less. It can sometimes be difficult to remember argument order with as few as two arguments!
If more arguments are needed, pass an “options” object as the last function argument.
An options object as the last function argument allows you to change the number of arguments without refactoring all references to that function.
There are only two hard problems in Computer Science: cache invalidation, naming things, and off-by-one errors.
Naming
Pick obvious names that are representative of the value or behavior.
Choose names that help your code read like a narrative.
Use long names if they’re appropriate. Tab-completion makes typing them easy, and finding all uses of a unique name is a simple task.
Define all “magic” numbers or strings as variables to explicitly communicate what they represent. For example, NUMBER_OF_SECONDS_IN_A_DAY is easier to understand than 86400.
Check for namespace collisions. Will the name you choose cause confusion with another existing name?
Code Comments
When To Use Comments
API documentation, often for use with tools like Javadoc, JSDoc, Swagger, etc.
To specify or ignore linter rules, for tools like ESLint (JavaScript), flake8 (Python), etc.
Describing unusual code that you were forced to include for non-obvious reasons, e.g. to circumvent a known bug or deal with a poorly-written API. (Include a link to the issue, if possible.)
Illustrating program flow or data handling in code samples
Pseudocode, but only as temporary placeholders for actual code
When Not To Use Comments
Describing normal or conventional functionality. The code should describe itself.
Describing complex functionality. Refactor your code instead.
Preventing code from being executed, except for temporary debugging.
Keeping code around “just in case”. Delete it and rely on version control.
Managing “todos” or describing changes that need to happen later. Use an issue-tracking system.
Reasons to Refactor Code
DRY-ness
Simplicity
Clarity
Testability
Refactoring for DRY-ness
DRY = Don't Repeat Yourself
Extract repeated patterns from your code to helper functions.
Define common constants in a shared file.
Avoid over-generalizing functions, classes, or objects. Consider using function composition, inheriting from superclasses, or partitioning objects.
Refactoring for Simplicity
Avoid nesting. Try extracting nested code to external functions.
Avoid clever code. Cleverness often conceals functionality — save it for high-performance or coding challenges.
Refactoring for Clarity
Limit functions or methods to one purpose.
Move transformational code into separate functions with meaningful names.
Ensure magic numbers and strings are replaced by meaningful constants.
Don’t be afraid to create one-line functions!
Write more verbose code if it facilitates understanding and doesn’t adversely impact performance.
Refactoring for Testability
Only export what needs to be tested.
Don’t (deeply) test third-party code.
Get configuration out of your code. Use a file, database, environment variables, etc.
Use dependency injection to simplify testing of external services, varying environments, or complex objects.
Safety Nets
Testing
Good tests can explain how your code should work without looking at the code itself.
Only test what you need to test.
Bad tests can do more harm than good.
Automated Testing
Set up your development environment to “watch” files and automatically execute tests when you save changes.
Use a continuous integration testing service that executes your test suite after code is committed to source control.
Using Source Control
Work in a feature branch.
Commit early and often.
Write meaningful commit messages.
"Squash" your changes prior to merging.
Learn to use your tools before an emergency arises.
Using Third-Party APIs
Consider wrapping your APIs with generalized wrappers if you think a service you’re using may eventually need to be replaced. This lets you swap out the underlying service with minimal disruption to the rest of your code.
Documentation
Provide instructions for installing, further developing, testing, contributing to, and using your project.
If your code and tests are written with clarity, you may not need extensive documentation.
Leverage tools to document the code for you.
Continuous Improvement
Tools for Success
Use linters to identify problems as you type — eslint, pylint, etc.
Use “beautifiers” to format code according to agreed-upon conventions — JavaScript Standard Style, Black for Python, gofmt, etc.
Use EditorConfig and pre-commit hooks to enforce consistency.
Enable syntax highlighting.
Leverage type-checking if that’s an option.
Consider Dockerizing your entire development environment. (See also: VS Code's devcontainer.json.)
Use an IDE that facilitates all of the above with as little effort as possible.
Choosing new tools
Keep an open mind. Just because a tool or framework is popular does not necessarily mean it’s the best solution for your problem.
Find out what other people are using or creating, and why. Read books, blogs, and newsletters. Listen to podcasts. Watch videos. Attend meetups and conferences.
Don't "reinvent the wheel" unless you have a good reason to do so.