How to write more readable code
Today I am going to share with you 5 tips on how to write more readable code. I am using these tips on a daily level and they are my must-haves. So, let's dig in:
Name it right
Always follow the naming convention! Have in mind that naming convention isn't always the same for all languages so you will probably have to look up and explore, but that time isn't wasted, it is invested.
Name your project, sub-projects, layers, packages, modules, and everything else descriptively with the following thought in mind: "Someone else will read my code and fix my bugs". Don't try to make bugfixer's life harder, he is your friend!
The same is with your variables, objects, classes, and all parts of codes. I have my personal rules which I follow:
- don't use shorthands,
- meaningful means better,
- variables are nouns, methods are verbs,
- bool variables should come with the "is" or "has" prefix.
Example:
# Bad examples
a: int = 3
wtxt: str = "Hi, there!"
successful: bool = True
def translation() -> str:
return "English translation"
# Good examples
number_of_rounds: int = 3
welcome_text: str = "Hi, there!"
is_successful: bool = True
def get_translation() -> str:
return "English translation"
Respect immutability
If you ask me immutability is one of the core principles. You should respect it. Besides making your code cleaner, it guards you against dumb mistakes where you accidentally mutate variables. Basically, I prefer defining a new variable whenever I should update the old one, which should stay the same.
from typing import List
# Bad example
def get_emails() -> List[str]:
emails: List[str] = []
try:
with open('users.txt', 'r') as file:
# Now imagine I did something fancy and I changed the emails variable
emails = ['me@kostakuu.rs'] # This is just mock data
except Exception:
# Imagine I logged exception
pass
return emails
# Good example
def get_emails_better() -> List[str]:
try:
with open('users.txt', 'r') as file:
# Now imagine I did something fancy and I changed the emails variable
return ['me@kostakuu.rs'] # This is just mock data
except Exception:
# Imagine I logged exception
pass
return []
One level of nesting is okay, two are a call for refactoring
With every level of nesting, our code becomes less readable and more complex. So, I prefer to have only one level of nesting if I need nesting, if I have two then it is a refactor time.
By doing this, we are not only getting more readable code, but it will be easier to maintain and debug. Let's take a look at the previous example:
from typing import List
# Bad example
def get_emails() -> List[str]:
try:
with open('users.txt', 'r') as file:
# Now imagine I did something fancy and I changed the emails variable
return ['me@kostakuu.rs'] # This is just mock data
except Exception:
# Imagine I logged exception
pass
return []
# Good example
def try_to_read_emails() -> List[str]:
try:
return get_emails_better()
except Exception:
# Imagine I logged exception
pass
return []
def get_emails_better() -> List[str]:
with open('users.txt', 'r') as file:
# Now imagine I did something fancy and I changed the emails variable
return ['me@kostakuu.rs'] # This is just mock data
Avoid bool parameters
In most of the cases if you have a bool parameter you will perform some checks and have if statement(s). I don't say you will always have this behavior, but I am sure that you can remember that you used the bool parameter at least once to violate the single responsibility principle. Those methods often contain "or" in the name, but that is not a strong rule.
# Bad example
def get_logged_in_user_email_or_mock(mock_email: bool = False) -> str:
if mock_email:
return 'mock@kostakuu.rs'
else:
# Imaging I did something nice and read real email
return 'me@kostakuu.rs'
# Good example
def get_logged_in_user_email() -> str:
# Imaging I did something nice and read real email
return 'me@kostakuu.rs'
def get_mocked_logged_in_user_email() -> str:
return 'mock@kostakuu.rs'
Polymorphism over IFs/enums
How many times did you see that some "type" variable is used to determine the execution path of the method? I saw that more times that I would like to admit.
And, again if we have that "type" that means that we will have nesting. Great... In order to keep workflow the same, but code much better, we can use polymorphism. With it, we can specify some default template (base class/interface) and all cases (derived classes).
from abc import ABC
def send_email(address: str) -> None:
# Imagine I sent the email
pass
def send_push_notification(address: str) -> None:
# Imagine I sent the push notification
pass
# Bad example
class Subscription:
def __init__(self, channel: str, where: str):
self.channel = channel
self.where = where
def notify(self) -> None:
if self.channel == 'email':
send_email(self.where)
elif self.channel == 'push':
send_push_notification(self.where)
raise Exception("Channel not implemented yet...")
# Good example
class SubscriptionBase(ABC):
def __init__(self, channel: str, where: str):
self.channel = channel
self.where = where
def notify(self) -> None:
pass
class EmailSubscription(SubscriptionBase):
def notify(self) -> None:
send_email(self.where)
class PushSubscription(SubscriptionBase):
def notify(self) -> None:
send_push_notification(self.where)
I hope you liked today's article. All examples are available at github.com/kostakuu/how-to-write-more-reada..
See you soon!
Insatiable hunger for learning
That's a cool post Let me state the conventions for JAVA:
- Class name must be a Noun that start with Upper case letter
- Interface name must be Adjective with Upper case letter at start
- Method name must be Verb with lower case letter at start
- Variable name can be noun/verb with lower case letter at start
- Package name should start with lower case letter, should represent the role and words should be seperated by (.) Dot character
- Constants should contain all Upper case letters and words are seperated by underscore
In general words are seperated by Camel Casing
I am glad that you enjoyed reading and thank you for sharing conventions for Java
Comments (2)