
Photo by Author | Ideogram
If you have been coding in Azar for a while, you have probably mastered the basics, making some projects. And now you are seeing your code thinking: “It works, but … this is not something I proudly show in a code review.” We are all there.
But when you keep coding, writing a clean code is as important as writing the functional code. In this article, I have compiled a practical technique that can help you go from “it does not touch,” it is actually intact.
🔗 🔗 Link from the code on the Gut Hub
1. Model data clearly. Do not pass around the duct
The dictionaries are extremely flexible in Azar and this is a particularly problem. When you pass through the raw dictionary in your code, you are inviting types, key errors and confusion as to which data should actually exist.
Instead of:
def process_user(user_dict):
if user_dict('status') == 'active': # What if 'status' is missing?
send_email(user_dict('email')) # What if it's 'mail' in some places?
# Is it 'name', 'full_name', or 'username'? Who knows!
log_activity(f"Processed {user_dict('name')}")
This code is not strong because it assumes that the dictionary keys are available without verification. This offers no protection from types or lost keys, causing KeyError
Run Times exceptions. There is no document that the fields are expected.
Do this:
from dataclasses import dataclass
from typing import Optional
@dataclass
class User:
id: int
email: str
full_name: str
status: str
last_login: Optional(datetime) = None
def process_user(user: User):
if user.status == 'active':
send_email(user.email)
log_activity(f"Processed {user.full_name}")
Of azagor @dataclass
The decorator provides you with a clean structure with a minimal boiler plate. Your IDE can now automatically provide for attributes, and if the desired fields are missing, you will get instant mistakes.
Consider PY, PYDANTIC for more complex verification:
from pydantic import BaseModel, EmailStr, validator
class User(BaseModel):
id: int
email: EmailStr # Validates email format
full_name: str
status: str
@validator('status')
def status_must_be_valid(cls, v):
if v not in {'active', 'inactive', 'pending'}:
raise ValueError('Must be active, inactive or pending')
return v
Now your data itself verifies itself, catching mistakes soon, and clearly document expectations.
2. Use anams for known choice
Strings are the victims of literary types and no IDE is automatically provided. Verification is only on the run time.
Instead of:
def process_order(order, status):
if status == 'pending':
# process logic
elif status == 'shipped':
# different logic
elif status == 'delivered':
# more logic
else:
raise ValueError(f"Invalid status: {status}")
# Later in your code...
process_order(order, 'shiped') # Typo! But no IDE warning
Do this:
from enum import Enum, auto
class OrderStatus(Enum):
PENDING = 'pending'
SHIPPED = 'shipped'
DELIVERED = 'delivered'
def process_order(order, status: OrderStatus):
if status == OrderStatus.PENDING:
# process logic
elif status == OrderStatus.SHIPPED:
# different logic
elif status == OrderStatus.DELIVERED:
# more logic
# Later in your code...
process_order(order, OrderStatus.SHIPPED) # IDE autocomplete helps!
When you are dealing with a fixed set of options, an Annum makes your code stronger and its own documentation.
With Anumus:
- Your IDE provides automatic tips
- Types (almost) become impossible
- You can repeat through all possible values ​​when needed
Anum prepares a set of nominees. Sort of indication status: OrderStatus
Expected parameter type documents. Using OrderStatus.SHIPPED
Instead of the wire literally, IDE automatically allows and grabs the type of type at the time of development.
3. Use only keyword arguments for explanation
The flexible argument system is powerful, but when the function calls contain several optional parameters, it can cause confusion.
Instead of:
def create_user(name, email, admin=False, notify=True, temporary=False):
# Implementation
# Later in code...
create_user("John Smith", "john@example.com", True, False)
Wait, what do these bolds mean again?
When called with location arguments, it is unclear what the Bolian values ​​represent without examining the definition of the function. Is true for admin, inform, or anything else?
Do this:
def create_user(name, email, *, admin=False, notify=True, temporary=False):
# Implementation
# Now you must use keywords for optional args
create_user("John Smith", "john@example.com", admin=True, notify=False)
*, Syntax forces all the arguments then by keywords. This shows your function itself and prevents the problem of “mystery bolin” where readers cannot tell what is right or wrong without reading the definition of the function.
This sample is especially useful in API calls and so on, where you want to ensure the explanation on the call site.
4. Use Pathlib on os.path
The OS.Path module of Azigar is active but clunky. The new Pathalib Module provides an object -based approach that is more intuitive and less error.
Instead of:
import os
data_dir = os.path.join('data', 'processed')
if not os.path.exists(data_dir):
os.makedirs(data_dir)
filepath = os.path.join(data_dir, 'output.csv')
with open(filepath, 'w') as f:
f.write('results\n')
# Check if we have a JSON file with the same name
json_path = os.path.splitext(filepath)(0) + '.json'
if os.path.exists(json_path):
with open(json_path) as f:
data = json.load(f)
It uses string manipulation with os.path.join()
And os.path.splitext()
To deal with the path. The works of the path are scattered in various functions. The code is functional and less intuitive.
Do this:
from pathlib import Path
data_dir = Path('data') / 'processed'
data_dir.mkdir(parents=True, exist_ok=True)
filepath = data_dir / 'output.csv'
filepath.write_text('results\n')
# Check if we have a JSON file with the same name
json_path = filepath.with_suffix('.json')
if json_path.exists():
data = json.loads(json_path.read_text())
Why the Pethalib is better:
- Is more intuitive / to join with it is more intuitive
- Ways such as
mkdir()
For, for, for,.exists()
Andread_text()
The pathways are connected to the Object - Works such as changing the extensions (with_Sufx) are more meaningful
Pathalib handles the path of manipulation in various operating systems. This makes your code more portable and stronger.
5. Failure to faster with the guard clauses
If it is often difficult to understand and maintain statements in a deep nest. Early Return – Guard Classes Use – leads to high reading code.
Instead of:
def process_payment(order, user):
if order.is_valid:
if user.has_payment_method:
payment_method = user.get_payment_method()
if payment_method.has_sufficient_funds(order.total):
try:
payment_method.charge(order.total)
order.mark_as_paid()
send_receipt(user, order)
return True
except PaymentError as e:
log_error(e)
return False
else:
log_error("Insufficient funds")
return False
else:
log_error("No payment method")
return False
else:
log_error("Invalid order")
return False
It is difficult to follow the deep nest. In each conditional block, several branches are needed simultaneously.
Do this:
def process_payment(order, user):
# Guard clauses: check preconditions first
if not order.is_valid:
log_error("Invalid order")
return False
if not user.has_payment_method:
log_error("No payment method")
return False
payment_method = user.get_payment_method()
if not payment_method.has_sufficient_funds(order.total):
log_error("Insufficient funds")
return False
# Main logic comes after all validations
try:
payment_method.charge(order.total)
order.mark_as_paid()
send_receipt(user, order)
return True
except PaymentError as e:
log_error(e)
return False
The guard’s clauses handle error cases, which reduces the level of inscription. Each condition is examined in order, making it easier to follow the flow. The central logic comes to the end, which is clearly separated from the error.
This approach is much better because your logic increases in complexity.
6. Do not use the list of the list more
Understanding of the list is one of the most beautiful features of Azigar, but they are able to read when complex conditions or changes are overloaded.
Instead of:
# Hard to parse at a glance
active_premium_emails = (user('email') for user in users_list
if user('status') == 'active' and
user('subscription') == 'premium' and
user('email_verified') and
not user('email') in blacklisted_domains)
The understanding of this list packs a lot of logic in one line. It’s difficult to read and debug. Many conditions are tied together, making it difficult to understand the quality of the filter.
Do this:
Here are the best alternatives.
Option 1: Work with a descriptive name
The complex state draws out in a designated function with a descriptive name. The understanding of the list is now more pronounced, focusing on what it (removing emails) is instead of filtering instead.
def is_valid_premium_user(user):
return (user('status') == 'active' and
user('subscription') == 'premium' and
user('email_verified') and
not user('email') in blacklisted_domains)
active_premium_emails = (user('email') for user in users_list if is_valid_premium_user(user))
Option 2: Traditional loop when the logic is complicated
Initially uses a traditional loop that continues to explain. Each condition is examined separately, making it easier to debug on which condition is failing. The logic of change is also clearly different.
active_premium_emails = ()
for user in users_list:
# Complex filtering logic
if user('status') != 'active':
continue
if user('subscription') != 'premium':
continue
if not user('email_verified'):
continue
if user('email') in blacklisted_domains:
continue
# Complex transformation logic
email = user('email').lower().strip()
active_premium_emails.append(email)
List understanding should be enabled to read your code more, not less. When the logic becomes complicated:
- Break the complex conditions in designated functions
- Consider the use of a regular loop with the ongoing continuing
- Divide complex operations into multiple stages
Remember, the purpose is to read.
7. Write the Pure Pure functions again
A function is a pure function if he always produces the same output for the same inputs. Also, it has no side effects.
Instead of:
total_price = 0 # Global state
def add_item_price(item_name, quantity):
global total_price
# Look up price from global inventory
price = inventory.get_item_price(item_name)
# Apply discount
if settings.discount_enabled:
price *= 0.9
# Update global state
total_price += price * quantity
# Later in code...
add_item_price('widget', 5)
add_item_price('gadget', 3)
print(f"Total: ${total_price:.2f}")
It uses a global state (total_price
) Which makes the test difficult.
The side effects of the function are (to modify the global state) and it depends on the external state (inventory and settings). This makes it unexpected and difficult to reuse.
Do this:
def calculate_item_price(item, price, quantity, discount=0):
"""Calculate final price for a quantity of items with optional discount.
Args:
item: Item identifier (for logging)
price: Base unit price
quantity: Number of items
discount: Discount as decimal
Returns:
Final price after discounts
"""
discounted_price = price * (1 - discount)
return discounted_price * quantity
def calculate_order_total(items, discount=0):
"""Calculate total price for a collection of items.
Args:
items: List of (item_name, price, quantity) tuples
discount: Order-level discount
Returns:
Total price after all discounts
"""
return sum(
calculate_item_price(item, price, quantity, discount)
for item, price, quantity in items
)
# Later in code...
order_items = (
('widget', inventory.get_item_price('widget'), 5),
('gadget', inventory.get_item_price('gadget'), 3),
)
total = calculate_order_total(order_items,
discount=0.1 if settings.discount_enabled else 0)
print(f"Total: ${total:.2f}")
The following version uses pure functions that take all dependence as parameters.
8. Write documents for public functions and classes
The documents are not (and should not be) after one thought. This is the main part of the maintaining code. Good DOC documents not only tell what works, but also why they are present and how to use them properly.
Instead of:
def celsius_to_fahrenheit(celsius):
"""Convert Celsius to Fahrenheit."""
return celsius * 9/5 + 32
This is a minimum document that only repeats the function name. Provides any information about parameters, return values, or edge matters.
Do this:
def celsius_to_fahrenheit(celsius):
"""
Convert temperature from Celsius to Fahrenheit.
The formula used is: F = C × (9/5) + 32
Args:
celsius: Temperature in degrees Celsius (can be float or int)
Returns:
Temperature converted to degrees Fahrenheit
Example:
>>> celsius_to_fahrenheit(0)
32.0
>>> celsius_to_fahrenheit(100)
212.0
>>> celsius_to_fahrenheit(-40)
-40.0
"""
return celsius * 9/5 + 32
A good docstring:
- Documents parameters and withdrawal values
- Notes any exception that may be lifted
- Provides examples of use
Your dostings act as a viable document that lives in harmony with your code.
9. Automatic lining and formatting
Do not rely on styling problems and manual inspection to catch ordinary insects. Automatic tools can handle the painful work to ensure the quality and consistency of the code.
You can try to configure these lining and formatting tools:
- Black – code formator
- Leopard – sharp linter
- MyPY – static type checker
- isort – Imported Organizer
Connect them using Pre -Committee Hooks to automatically check and form the code before each covenant:
- Pre -Committee Install: Code styling = “Background: #F5F5F5;”> Install PIP Pre -Committee
- Make a code styling = “Background: #F5F5F5;”>. Pre-commit-config.yaml filed with file tools
- Run Code Style = “Background: #F5F5F5;”> Install to activate pre -commit
This setup ensures permanent code style and catchs mistakes soon without manual effort.
You can check 7 tools to help you write better codes to find out more.
10. Except for the catch to avoid
The general exception handlers hide the insects and make debugging difficult. They catch everything, including syntax mistakes, memory errors, and keyboard barriers.
Instead of:
try:
user_data = get_user_from_api(user_id)
process_user_data(user_data)
save_to_database(user_data)
except:
# What failed? We'll never know!
logger.error("Something went wrong")
It uses a naked exception to handle:
- Programming errors (such as syntax errors)
- System errors (like memory.)
- Keyboard interference (CTRL+C)
- Expected errors (such as a network timout)
This makes debugging extremely difficult, as all the mistakes are treated the same.
Do this:
try:
user_data = get_user_from_api(user_id)
process_user_data(user_data)
save_to_database(user_data)
except ConnectionError as e:
logger.error(f"API connection failed: {e}")
# Handle API connection issues
except ValueError as e:
logger.error(f"Invalid user data received: {e}")
# Handle validation issues
except DatabaseError as e:
logger.error(f"Database error: {e}")
# Handle database issues
except Exception as e:
# Last resort for unexpected errors
logger.critical(f"Unexpected error processing user {user_id}: {e}",
exc_info=True)
# Possibly re-raise or handle generically
raise
Catching specific exceptions that can be expected and can be handled properly. Each exception type has its own error message and handling strategy.
The final holds the final unpredictable mistakes except discount, logs them with full traceback (exc_info=True
), And raises them again to avoid serious issues.
If you need a catch all -handler for some reason, use except Exception as e:
Instead of naked except:
And always log in with full details of exception exc_info=True
.
Wrap
I hope you have to use at least some of these methods in your code. Start implementing them in your projects.
It will be easier for you to maintain your code, more capable, and argue about it.
Next time you have the temptation to take a shortcut, remember: The code is read more times than his writing. Happy Clear coding?
Pray Ca Is a developer and technical author from India. She likes to work at the intersection of mathematics, programming, data science, and content creation. The fields of interest and expertise include dupas, data science, and natural language processing. She enjoys reading, writing, coding and coffee! Currently, they are working with the developer community to learn and share their knowledge with the developer community by writing a lesson, how to guide, feed and more. The above resources review and coding also engages lessons.