50 Coding Laws That Would Make You A Decent Programmer. - by Alexander Obidiegwu - Medium
50 Coding Laws That Would Make You A Decent Programmer. - by Alexander Obidiegwu - Medium
Become a member
1.97K 25
The internet has given everyone the right to voice an opinion. Including even
me. But in this article, we will be dealing with 50 Python best practices that
are set in stone.
These are practices that even God himself can’t tweak. These practices
differentiate the pro from the amateur and a lot of them can also be adapted
for various programming languages.
Most Python developers need somewhere to quickly test their code or debug errors. I
developed a website called python-fiddle.com which you can use to quickly test out
a code and it uses AI/LLMs to help find solutions to possible errors.
I plan to start posting content on linkedln too so if you’re interested: My Linkedln:
https://www.linkedin.com/in/alexanderobidiegwu
This can become very problematic as time passes and the code receives
updates or changes. At some point, the comment becomes a lie and
everyone now has to observe the truth through the lens of the lie.
In Law 14 and 15, you would learn when and when not to use a comment.
But when the variable type isn’t intuitive, rather than specifying the type
when naming the variable, the best way to do this is to use type annotations.
This way, everyone can tell the variable is a string while keeping the code
neat & concise.
It also makes the code very readable and less redundant. For example,
Goat.get_horn_length() instead of GetGoat.get_horn_length() .
It also eradicates the need for comments and allows any developer to
mentally conceptualize without having to look at the raw code.
This would help both you and the devs in your team know what to expect
without always having to use print statements to get a visual understanding.
Bad Practice
Run code
Good Practice
Run code
(Note that the methods and attributes are just for example purposes.)
Python fiddle
This checks if the address is valid and after checking returns the latitude and
longitude. The function does two things. Checks if the address is valid and
returns the geolocation of the address.
https://python-fiddle.com/saved/bQD3sPFuC4BWeTx1gdLr
The above functions do only one thing and nothing more. Although it may
seem more verbose, it’s a lot more concise and readable.
Another way you can tell is if it has more than one level of abstraction…
Good Practice
https://python-fiddle.com/saved/ezGiYtWK72HXiVm65azM?run=true
This function has statements that are of a low level of abstraction. Things
like sum , len, etc.
Bad Practice
Bad Practice
write(True)
Good Practice
write(name)
The second one is more descriptive of what exactly the function is doing. It is
clear to whoever reads this that we are writing a name.
The first one isn’t as explicit as the second. You have to make guesses and
possibly even have to view the entire function.
https://python-fiddle.com/saved/BfPEC7YYSEHQhz2xgGTW
But what is a clean code? A clean code is well structured and arranged.
A clean code doesn’t hide bugs. It exposes anywhere a bug could hide to the
programmer and makes room for an easy fix without complete refactoring.
https://python-fiddle.com/saved/6kfW6eccopYfotqaKlcR?run=true
https://python-fiddle.com/saved/AEbfUZvVQ0k55RyHcypB
This is a more robust solution because now we don’t need to modify either
the class or its functions. If we ever want to consider a country and its
capital, we can simply adjust our capital dictionary.
For example:
This way, whenever we need to add a new payment option, say crypto or
paypal, we wouldn’t need to edit or modify any class to achieve this. we could
simply do this:
LAW 13: Liskov Substitution Principle
If we look at the previous principle, when making payments using crypto, we
don’t exactly specify the cryptocurrency we are transferring. We only specify
an amount. So suppose we want to specify a cryptocurrency, we would
usually do something like this:
class PaymentProcessor:
@abstractmethod
def pay_tax(amount, crypto):
pass
Then when calling each payment processor, we declare the crypto argument
as a None type or give it a default value to avoid passing in any argument if
Top highlight
not needed. Both cases fail to adhere to the Liskov Substitution Principle.
This is because the parent class or the abstract class, contains an argument
that isn’t relevant to most of our subclasses
def pay_tax(amount):
print(f'Your {self.crypto} wallet is being processed for tax payment')
print(f'You are to be charged {amount}')
Informative comments
Making comments informative can always help express the code to the
reader. A comment that highlights the return value of a function, for
instance, would provide more clarity. But comments like this can be made
redundant through the use of well-descriptive functions or variable names.
TODO comments
Comments like this help other programmers know that this is an unfinished
function/task or it requires modification. Maybe there’s a better way to
implement a certain function that wasn’t taken advantage of. Maybe the code
fails periodically.
Regardless of your reason, comments like this provide more value than they
take. TODO comments are also less likely to be left untouched as the code
changes or improves because they are usually remembered to be removed
once the task has been completed or modified properly.
Warning of consequences
Sometimes, we want to tell other developers about potential landmines.
Stepping on these landmines could have some unforeseen consequences.
And we want to all survive through the day. Comments can save the day in
this situation.
Unobvious comments
We often write comments that seem obvious to us but not to someone else.
The connection between your comments and the function referenced must
be clear. They must both follow the same step or procedure at sync. You
don’t want your comment to need to have its comment as well.
Short Functions
Most likely we do not need a comment for a short function. The
shorter/smaller the function, the more likely it can be described with a good
name. Hence, they are usually self-descriptive.
Can you remember when you had to scroll all the way to the top of the script,
just to find out what a function does and how it relates to where it’s being
called?
The worst part of this is you can’t understand the relation in one glance. It’s a
back-and-forth movement. Once you’ve experienced this, you’ll understand
how valuable it is to keep related code to each other.
def create(name):
print(name)
But when it comes to working with other devs, you want to dumb down those
personal preferences and adopt the team’s preferences. Not everyone might
be able to see beauty in your code the way you do.
For example, let’s say you have a Page that displays the last 50 Orders in a
“Your Orders” Overview Page. 50 is the Magic Number here because it’s not
set through standard or convention, it’s a number that you made up for
reasons outlined in the spec.
Now, what you do is you have the 50 in different places — your SQL script
( SELECT TOP 50 * FROM orders ), your Website (Your Last 50 Orders), your
order login ( for (i = 0; i < 50; i++) ) and possibly many other places.
# Bad
SELECT TOP 50 * FROM orders
# Good
NUM_OF_ORDERS = 50
SELECT TOP NUM_OF_ORDERS * FROM orders
# Bad
if x:
if y:
do_something()
# Good
if x and y:
do_something()
# Bad
temp_result = calculate(x, y)
final_result = temp_result * 2
# Good
final_result = calculate(x, y) * 2
# Good
def calculate_total_price(quantity, unit_price):
pass
# Bad
file_path = "/path/to/file.txt"
# Good
import os
file_path = os.getenv("FILE_PATH")
You want to use try-catch statements when a certain code is more likely to
return an error.
Things like API requests, file handling, etc tend to fail or raise errors due to
one reason or the other. Using try-catch statements for multiplication or
division is just uncalled for and creates more problems than it solves.
Create informative error messages along with your exceptions which can be
done when printing the error. Mention the context in which the operation
failed and the type of failure.
Law 28: Avoid Using Multiple Exception Class
Have you ever seen a code like the one below…
try:
pass
except ValueError:
pass
except TypeError:
pass
except IndexError:
pass
except KeyError:
pass
except FileNotFoundError:
pass
This is extremely bogus and takes away (in verbosity, complexity, and
maintenance) the additional help it provides in error handling.
It’s often better to use a more general exception to catch any sort of error we
might come across. This type of exception, by default, includes the type of
error we got.
try:
pass
except Exception:
pass
Only be specific about the type of error you want to catch, when you want all
other errors to pass through.
LAW 29: A function must either mutate or return something, but not both.
Whenever we are creating a function, we should keep in mind what exactly
that function is supposed to do. Does it mutate the arguments passed? Or
does it need to return something?
But what do I mean by mutation? If the function changes the contents of the
argument(s) or changes the data type of the arguments, it mutates.
def changed(array):
array.append('hello')
If the argument(s) is used to create another variable, then it doesn’t mutate.
For instance, if an argument called time was used to calculate a distance ,
But there’s a way to get the best of both worlds. You can copy the argument(s)
of the function and perform mutation on those. This way we avoid any side
effects.
def changed(array):
array_copy = array[:]
array_copy.append(4)
return array_copy
This is a common convention built into Python itself. Methods such as sort,
and append are verbs because they mutate the data type and return None
while methods like sorted, sum, product are all nouns because they don’t
mutate any arguments passed and return a new copy of the data.
There are obviously exceptions to this and whenever you feel you’ve
encountered one, feel free to fall back on using verbs.
This takes us back to SRP (single responsibility principle) which states that a
class should only have one reason — one responsibility — to change.
class Animal:
def __init__(self, name):
self.name = name #instance variable
If all our functions are related to the class responsibility, there’s no reason
why we should have a lot of instance variables.
These functions tend to come with their own variables that other functions
in the class do not need.
# Bad
file = open("example.txt", "r")
data = file.read()
file.close()
# Good
with open("example.txt", "r") as file:
data = file.read()
# Bad
result = "even" if number % 2 == 0 else "odd" if number % 3 == 0 else "neither"
# Good
if number % 2 == 0:
result = "even"
elif number % 3 == 0:
result = "odd"
else:
result = "neither"
LAW 36: Use ‘is’ and ‘is not’ for Identity Comparison
Most of the time, we use == to check the comparison between two variables.
This is usually okay for immutable data types like strings or integers because
immutable objects with the same value are usually stored in the same
memory location, so a memory location check is not needed.
But when dealing with mutable data types, such as list, dicts and custom
objects, it’s often better to use the is comparator because it checks the
subtype of the variable and the memory location.
The memory location of mutable objects are usually not the same due to the
way Python works. Python stores mutable objects in different memory
locations because they can be changed at any time and each of them must be
independent of one another even though.
# Preferred: Using is
if list1 is list2:
print("Lists are the same object")
# Note: In this case, list1 and list2 are different objects with the same value,
# so using `is` would give a different result than `==`.
# Bad
class Logger:
def log(self, message):
with open('log.txt', 'a') as f:
f.write(message + '\n')
class Calculator:
def __init__(self):
self.logger = Logger()
In the example above, we define the class Logger and directly create a new
instance of it in our Calcuator class. This means that Calculator now
depends on Logger class and if for any reason we change Logger class, we
now also have to modify Calculator class.
And as you can see this also fails to adhere to the open closed principle
(open for extension, closed for modification).
This tight coupling makes the code harder to test because we can no longer
simply use a fake logger class when testing.
# good
from abc import ABC, abstractmethod
class LoggerInterface(ABC):
@abstractmethod
def log(self, message):
pass
class Logger(LoggerInterface):
def log(self, message):
with open('log.txt', 'a') as f:
f.write(message + '\n')
class Calculator:
def __init__(self, logger: LoggerInterface):
self.logger = logger
# Bad
assert x > 0, "x should be positive"
# Good
if x <= 0:
raise ValueError("x should be positive")
def calculate_discount(price):
discount = price * 0.1 # 10% discount
return price - discount
The example below uses the hard-coded number 0.1 to represent a 10%
discount.
def calculate_discount(price):
TEN_PERCENT_DISCOUNT = 0.1
discount = price * TEN_PERCENT_DISCOUNT
return price - discount
The improved code replaces the hard-coded number with a named constant
TEN_PERCENT_DISCOUNT . The name instantly conveys the meaning of the value,
making the code more self-documenting.
It also reduces the risk of errors and bugs as you only need to modify your
code in one place if you need to change or update it.
# Bad
# Good
immediate neighbors but should not have any knowledge further than that.
class Order:
def __init__(self, customer):
self.customer = customer
def get_customer_name(self):
# Violation: Order knows too much about the customer's structure
return self.customer.get_profile().get_name()
In this example, the Order class directly accesses the customer’s profile to
retrieve the customer’s name. This violates the Law of Demeter because
Order is reaching into the internal structure of the Customer object to access
its profile and name.
It has gone past an immediate neighbor and now knows too much about the
customer’s object.
class Order:
def __init__(self, customer):
self.customer = customer
def get_customer_name(self):
# Adherence: Order only interacts with its immediate collaborator
return self.customer.get_name()
In this adherent example, the Order class only interacts with its immediate
collaborator, the Customer object, and calls a method directly on it to retrieve
the customer’s name.
It does not reach into the internal structure of the Customer object, thus
following the Law of Demeter.
That is why the readability of code is always more important than its
conciseness when it comes to software development.
Imagine you want to write a function with the name get_file . You click g
This becomes more of a pain when you want to call that function. Your
function name could be lost in between the recommendations and now your
IDE becomes more of a problem than an efficient solution.
# Bad
from module import *
# Good
from module import symbol1, symbol2
This often means separating the startup process, — which is when our
dependencies and objects are wired together — , from the run time logic, —
which is when the logic of the application executes based on inputs from the
user or other triggers — .
One common way to separate construction from its use is to construct the
application logic in a file/function/module called main .
The main function builds the objects necessary for the application to run
smoothly. This frees other modules from being tightly coupled to the
application and promotes reusability and modularity.
# Bad
try:
try:
# Code that might raise errors
pass
except ValueError:
# Handle ValueError
pass
except Exception as e:
# Handle any other unexpected errors
pass
# Good
try:
# Code that might raise errors
pass
except ValueError:
# Handle ValueError
pass
except Exception as e:
# Handle any other unexpected errors
pass
It’s also very easy to write clean code when implementing very faulty
concurrency functionality. Usually, you might not even be aware that it is
faulty until a lot of stress is put on the system.
There are multiple reasons why your concurrency code might fail. Here are
some:
But as you grow in experience and skills, you want to be able to decide when
it’s best to follow a certain rule and when it’s not.
This gut feeling/intuition only comes to those who have mastered their craft
and if you’re a newbie or you just started your career 2 years ago, it’s usually
best you follow these laws like it’s your only ticket to heaven.
Most Python developers need somewhere to quickly test their code or debug errors. I
developed a website called python-fiddle.com which you can use to quickly test out
a code and it uses AI/LLMs to help find solutions to possible errors.
Programming Python Software Engineering Clean Code Data Science
1.7K Followers
5 min read · Dec 25, 2023 11 min read · Jan 29, 2024
1.2K 7 81
33 2 400 1
Lists
Why Can’t Robots Click The “I’m Why I Keep Failing Candidates
Not a Robot” Box On Websites? During Google Interviews…
Clicking a tiny box tells Google all they need They don’t meet the bar.
to know about your humanity
· 5 min read · May 24, 2024 · 4 min read · Apr 13, 2023
Help Status About Careers Press Blog Privacy Terms Text to speech Teams