Showing posts with label best practices. Show all posts
Showing posts with label best practices. Show all posts

Aug 6, 2020

Less lines is better (in most cases)

Less lines is better (in most cases)

In most cases less code is better. "The best code is no code at all." - Jeff Atwood.

Googling "less code is better" gives some good articles about the statement. I would list some of them here:

And some other:

Instead of a disclaimer: "Readability matters, and you should not sacrifice readability in order to get less code." (c) Daan

In this particular article I would like to describe my view on the special case of having less code. That is having less lines of code. Although strictly speaking it is possible to have less lines of code with the same, less and even more code itself.

Here are some reasons why I would prefer less lines of code (without sacrificing readability):

  • Vertically more compact code
  • Less need for vertical scrolling
  • Smaller commit diffs
  • Possibly less code

The most important reasons are "Vertically more compact code" and "Less need for code scrolling". Both reasons let you reduce the number of bugs (per feature) and also code faster.

Vertically more compact code lets you see more source code at a time. And therefore frees your brain capacity from remembering the code that is currently not shown on the screen. The freed brain capacity can be used for analyzing the code and being more attentive which results in faster development and less bugs respectively.

Less need for vertical scrolling also results into less bugs because it reduces brain distractions on the scrollings. There are also some little saving because scrolling the code does require some actual time to do the scrolling. Also scrolling may introduce human factor bugs because of mislooking parts of the code related to their vertical position in a file.

Several years ago I read some research conclusions backing the above claims, but unfortunately I could not find proof links for them now. Please, submit links in comments if you have them.

There are some techniques to reduce negative effects of having many code lines:

  • Second, third, etc monitor (especially vertically positioned)
  • Smaller font sizes, so more code lines can fit vertically on a screen
  • Breaking long files into several smaller ones

Each of these techniques should be used if possible in addition, but not as a replacement to less code lines. And here is why.

Extra monitors is a great option. I remember I read a research conclusion that an extra monitor might increase productivity up to 30%. Unfortunately, although extra monitors help to reduce amount of computer interface interactions (scrolling and switching between files) they still do not completely solve the focus and attention issue. Because a human still able to look at only one screen at a time even if several of them are in front. Another issue with extra monitors is that they limit your mobility which might be important for those who often work on a laptop from different places (basically the extra monitor option might be just not option for such users).

Smaller font size let you display more lines on the same screen, but at an expense of more load on eyes. Which may be undesirable for health reasons and in fact make your eyes getting tired faster.

Breaking long files into several smaller ones solves a need for scrolling by a need for switching between files. Which may be a better option, but still has a drawback of distracting developer's attention for extra interface interactions.

There is something else about having more lines. When reading the code line by line every line switch requires some brain capacity for understanding if the next lines expressing another logical construct of the language or a continuation of the current one. As an example I would put a multiple line function or method call. So with each next line you read you need to understand if it is still the same function call that started several lines above or a new function call or statement. Therefore the less lines there are the less times you need figure out if you are still in the same logic construct or there is a new one started.

Based on the above explanation I would like to provide some otherwise questionable code style decisions that I prefer.

Multiline one-liners

Prefer:

value = (value_for_true() if some_looooooooooooong_expression(arg) else
         value_for_false())

over:

if some_looooooooooooong_expression(arg):
    value = value_for_true()
else:
    value = value_for_false()

This is an example of the same code size, but different number of lines. The preferred variant is 2 lines less and twice smaller. I should point out that the preferred variant is intact with Google Python Style Guide.

Condensed function/method calls

Prefer:

value = function(long_name_arg1, long_name_arg2,
                 long_name_kwarg1=value1, long_name_kwarg2=value2)

over:

value = function(
    long_name_arg1, long_name_arg2,
    long_name_kwarg1=value1, long_name_kwarg2=value2)

and especially over:

value = function(
    long_name_arg1,
    long_name_arg2,
    long_name_kwarg1=value1,
    long_name_kwarg2=value2,
)

All the above snippets are PEP8 complaint. But with preferred one we can save up to 4 lines and make the code up to 3 times lines shorter.

Dec 7, 2016

My Python software development practices

UPDATE ON 2022-11-12

This post is continuously updated list of software development practices that I use every day developing software in Python. They include coding conventions, software design principles and software project management principles.

Well-known conventions and practices

Extensions, exceptions and customization to well-known conventions and practices from the previous section

Python-specific

  • Python code maximum line length is 100 or 120 characters
  • Preferred order of attributes within a class:
    • NAMED_CONSTANTS
    • class data attributes
    • __init__() if present
    • __magic_methods__()
    • @properties
    • @staticmethods
    • @classmethods
    • _private methods
    • public methods (related private and public methods should be placed together for better readability)
  • When overriding a method always call an overridden parent class method (with super) and return its result (if you are not altering it) even if the overridden method is not supposed to return anything then None at the moment (it is a forward compatibility measure)
  • Usage of introspection (e.g. getattr(), hasattr()) and __magic_methods__ override should be well-considered. If a feature can be implemented without introspection or explicit __magic_methods__ it should implemented with out them (with very rare exceptions). Usage of introspection or explicit __magic_methods__ usually is a sign of bad code design or "reinvention of the wheel"
  • Use strict version dependencies to prevent unexpected upgrades. Apply the same rule to the dependencies of your dependencies recursively. Example: dependency-package-name==x.y.z (explanation) → Use poetry
  • Use Python packaging instead of requirements.txt and git-based deploys → Use poetry

Development Process

  • Collective code ownership
  • Code review is a good investment of your time
  • Favor code development performance (productivity) over code run-time performance (with reasonable trade-offs)
  • Favor good enough code and code development performance over perfect code when you have tight deadlines. You can always improve later
  • It is fine to write an optimal code in the first place (in terms of size, performance or resource usage) if it comes at zero or near zero cost (we should not deoptimize code on purpose to avoid being accused in premature optimization).
  • Dedicate time on code run-time performance optimization only if it is really needed. Premature optimization may result in waste of time
  • Every time you put a dirty hack or just something that can be done better into your code put a TODO near it with an explanation and/or description of actions for improvement. It will help to track technical debt and help other developers during refactoring or regular development to understand if they are right to judge this code as strange and to be improved
  • If it is hard to choose between alternative technical solutions, then choose any of them instead of wasting time trying to make the right choice. Later when you have more information or circumstances change you can refactor if the original choice was wrong
  • Using a language feature just to show others that you know the feature is unprofessional
  • Manage to see more lines at the screen at the same time for lesser defect count (vertical orientation of display and meaningless lines elimination may help)
  • Keep development environment close to production as much as possible
  • Keep testing environment identical to production environment (including the deployment procedure)
  • Never submit changes that are known to break something that already works
  • Never remove something that you do not understand or because you do not understand it

Code Design

  • Write code to be read by humans
  • Favor code readability over code run-time performance (with reasonable trade-offs)
  • Maintain code reuse of own code and reuse code from publicly available libraries. Reinventing the wheel is a waste of time
  • Avoid code copy & paste unless you have very strong reasons for it
  • Know the difference between agile and universal: instead of writing a code for all imaginary future use cases write the code that can be easily adapted to many of the future use cases (also known as maintainable code)
  • Maintain the least possible cyclomatic complexity of the code for better readability and smaller defect count
  • Favor a readable code over a well commented code. A necessity for comment is good sign of a poor readability of the code
  • Consider writing a logging message instead of a comment. It will serve two purposes
  • Code run-time performance optimization should start from identifying bottlenecks and their elimination instead of something that is easy to optimize
  • If a literal used twice in the code then prefer putting it to a named constant. Even literals that occur only once deserve to be put in a named constant, because named constant will describe the nature of the literal
  • Do not put .gitignore into git repository. Every developer should be free to ignore whatever extra file he/she has locally (.gitignore should not aggregate every developer's local "mess") → Use .git/info/exclude for developer specific ignores

Code style

  • Maintain a shorter code size in number of lines and characters for better readability and lesser defect count unless it impacts performance or readability
  • TODO format: TODO(author) <LOW | MEDIUM | HIGH | CRITICAL>: textual description of todo
  • A longer variable, function, class or method name is better than an unclear or ambiguous name
  • Code style should be consistent across the entire code base
  • Code base should not contain commented out source code unless it is a part of a comment or TODO.
  • Non-ASCII characters should not be a part of the source code. Presence of such characters is a sign of poor localization, internationalization or parameterization of the code 

Organizational (may be boring for some developers)

  • Every decision should be reasonable (based on reasons)
  • Actual responsibility for a decision always lays on the person(s) who made the decision independent of how it cooked or formalized
  • Every decision should be considered in every longevity term (short, medium and long)
  • Every technical decision should be made considering its effect on business as the most important criteria
  • Technical solutions of a highest efficiency (= effect / expenses) should be chosen during the decision making process
  • Honor established processes, rules, methodologies and patterns, but do not hesitate to step out if it is beneficial in terms of profit and loss