folio / docs /coding-guidelines.md
dystomachina's picture
refactor(data-fetcher): simplify data fetcher to use only YFinance
203d1e9
# A Concise, Opinionated Guide to Writing Good Code (with Python examples)
This guide summarizes core principles for writing clean, maintainable, and effective code. It's opinionated and rule-based, designed to provide clear direction for junior developers. Adhering to these rules will help you build better software and become a more valuable team member. Python examples are provided for clarity.
## 1. Naming Matters Immensely
* **Rule:** Use intention-revealing names.
* **Don't:** `d = (datetime.now() - start_date).days`
* **Do:** `elapsed_time_in_days = (datetime.now() - start_date).days`
* **Rule:** Avoid disinformation.
* **Don't:** `account_list = {"id": 1, "name": "Alice"}` (It's a dictionary, not a list)
* **Do:** `account_data = {"id": 1, "name": "Alice"}` or `account_dict = ...`
* **Rule:** Use pronounceable and searchable names.
* **Don't:** `genymdhms = datetime.now().strftime('%Y%m%d%H%M%S')`
* **Do:** `generation_timestamp = datetime.now().strftime('%Y%m%d%H%M%S')`
* **Rule:** Be consistent.
* **Don't:** Using `fetch_user_data`, `getUserInfo`, `retrieve_client_details` in the same project.
* **Do:** Consistently use one style, e.g., `get_user_data`, `get_order_info`, `get_product_details`.
## 2. Functions Should Be Small and Focused
* **Rule:** Functions must do **one thing**.
* **Don't:**
```python
def process_user_data(user_id):
# Fetches data
response = requests.get(f"/api/users/{user_id}")
user_data = response.json()
# Validates data
if not user_data.get("email"):
raise ValueError("Email missing")
# Saves data
db.save(user_data)
# Sends notification
send_email(user_data["email"], "Welcome!")
return user_data
```
* **Do:** Break it down:
```python
def fetch_user_data(user_id):
response = requests.get(f"/api/users/{user_id}")
response.raise_for_status() # Raise HTTP errors
return response.json()
def validate_user_data(user_data):
if not user_data.get("email"):
raise ValueError("Email missing")
# ... other validations
def save_user_data(user_data):
db.save(user_data)
def send_welcome_email(email_address):
send_email(email_address, "Welcome!")
def register_user(user_id):
user_data = fetch_user_data(user_id)
validate_user_data(user_data)
save_user_data(user_data)
send_welcome_email(user_data["email"])
return user_data
```
* **Rule:** Functions must be **small**. (The "Do" example above also illustrates this).
* **Rule:** Minimize function arguments.
* **Don't:** `def create_user(name, email, password, dob, address, phone, role, is_active): ...`
* **Do:**
```python
class UserProfile:
def __init__(self, name, email, dob, address, phone):
# ... initialization ...
def create_user(profile: UserProfile, password: str, role: str, is_active: bool = True):
# ... use profile attributes ...
```
Or pass a dictionary:
```python
def create_user(user_details: dict):
# Access details via user_details['name'], user_details['email'] etc.
# Consider using TypedDict for better structure if using Python 3.8+
...
```
* **Rule:** Avoid side effects where possible.
* **Don't (Hidden Side Effect):**
```python
user_list = []
def add_user_if_valid(name, email):
if "@" in email:
user_list.append({"name": name, "email": email}) # Modifies global state
return True
return False
```
* **Do (Explicit):**
```python
def create_user_record(name, email):
if "@" not in email:
raise ValueError("Invalid email")
return {"name": name, "email": email}
# Usage
try:
new_user = create_user_record("Bob", "bob@example.com")
user_list.append(new_user) # State change happens outside the function
except ValueError as e:
print(f"Error: {e}")
```
## 3. Comments Are for "Why," Not "What"
* **Rule:** Comment the "Why," not the "What."
* **Don't:**
```python
# Check if user is eligible
if age >= 18 and country == "US": # This just repeats the code
is_eligible = True
```
* **Do:**
```python
# User must be a legal adult in the US to qualify for this specific offer.
if age >= 18 and country == "US":
is_eligible = True
```
* **Rule:** Do **not** leave commented-out code.
* **Don't:**
```python
def calculate_total(items):
total = 0
for item in items:
total += item['price']
# tax = total * 0.10 # Old tax calculation
# total += tax
total *= 1.10 # Apply 10% tax
return total
```
* **Do:** Remove the commented lines. Use Git history if you need to see the old calculation.
```python
def calculate_total(items):
total = sum(item['price'] for item in items)
total *= 1.10 # Apply 10% tax
return total
```
* **Rule:** Keep comments up-to-date. (Self-explanatory - if the logic changes, update or remove the comment).
* **Rule:** Avoid redundant comments.
* **Don't:**
```python
count = 0 # Initialize count
count += 1 # Increment count
```
* **Do:** Just the code is enough.
```python
count = 0
count += 1
```
## 4. Formatting and Structure Enhance Readability
* **Rule:** Use a consistent style guide (e.g., PEP 8 for Python). Use tools like `Black`, `Flake8`, `isort`.
* **Don't:** Inconsistent spacing, line lengths, import orders.
* **Do:** Code automatically formatted by tools like `Black`.
* **Rule:** Top-down narrative.
* **Don't:** Define helper functions *before* the main function that uses them, forcing the reader to jump around.
* **Do:**
```python
def main_process():
data = _fetch_data()
result = _process_data(data)
_save_result(result)
# --- Helper functions defined below ---
def _fetch_data(): ...
def _process_data(data): ...
def _save_result(result): ...
```
*(Note: Leading underscore `_` often indicates internal/helper functions)*
* **Rule:** Keep related concepts vertically close. (The example above also shows this).
* **Rule:** Use whitespace.
* **Don't:**
```python
def process(a,b,c):
x=a+b
y=x*c
if y>10:
print("Large")
else:
print("Small")
z=y-a
return z
```
* **Do:**
```python
def process(a, b, c):
intermediate_value = a + b
final_value = intermediate_value * c
if final_value > 10:
print("Large")
else:
print("Small")
adjusted_value = final_value - a
return adjusted_value
```
## 5. Keep It Simple (KISS & YAGNI)
* **Rule:** KISS (Keep It Simple, Stupid).
* **Don't:** Using complex metaprogramming or obscure language features when a simple loop or conditional would suffice.
* **Do:** Prefer straightforward, readable solutions.
* **Rule:** YAGNI (You Ain't Gonna Need It).
* **Don't:** Adding configuration options, database fields, or API endpoints for features that *might* be needed in the future but aren't required now.
* **Do:** Implement only what's necessary for the current requirements.
* **Rule:** Avoid premature optimization.
* **Don't:** Spending hours micro-optimizing a function with string concatenations before profiling to see if it's even a bottleneck.
* **Do:** Write clean code first. If performance is an issue (measure it!), profile and optimize the specific hotspots. Often, a better algorithm beats micro-optimization.
## 6. Don't Repeat Yourself (DRY)
* **Rule:** Avoid duplication.
* **Don't:**
```python
def process_file_a(path):
# 10 lines of validation logic
if not valid: return None
# Process file A specific logic
...
def process_file_b(path):
# Same 10 lines of validation logic copied here
if not valid: return None
# Process file B specific logic
...
```
* **Do:**
```python
def _validate_input(path):
# 10 lines of validation logic
return is_valid
def process_file_a(path):
if not _validate_input(path): return None
# Process file A specific logic
...
def process_file_b(path):
if not _validate_input(path): return None
# Process file B specific logic
...
```
## 7. Handle Errors Gracefully
* **Rule:** Use exceptions over error codes.
* **Don't:**
```python
def divide(a, b):
if b == 0:
return -1 # Error code
return a / b
result = divide(10, 0)
if result == -1:
print("Error: Division by zero")
```
* **Do:**
```python
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
try:
result = divide(10, 0)
except ValueError as e:
print(f"Error: {e}")
```
* **Rule:** Provide context with errors.
* **Don't:** `raise Exception("Error!")`
* **Do:** `raise ValueError(f"Invalid user ID format: '{user_id_str}'")`
## 8. Test Your Code
* **Rule:** Write unit tests (using frameworks like `pytest` or `unittest`).
* **Don't:** Skipping tests because the code "looks simple."
* **Do:**
```python
# Example using pytest
from my_module import add
def test_add_positive_numbers():
assert add(2, 3) == 5
def test_add_negative_numbers():
assert add(-1, -1) == -2
def test_add_mixed_numbers():
assert add(5, -3) == 2
```
* **Rule:** Test behavior, not implementation.
* **Don't:** Writing a test that checks if a specific private helper method (`_helper`) was called.
* **Do:** Writing a test that checks if the public method produces the correct output or state change, regardless of which internal helpers were used.
* **Rule:** Keep tests clean, readable, and fast. (Apply the same principles from this guide to your test code).
## 9. Practice Continuous Refactoring
* **Rule:** Follow the Boy Scout Rule.
* **Don't:** Seeing a poorly named variable or a slightly complex block of code and leaving it because "it works."
* **Do:** Taking a few moments to rename the variable or extract a small function to improve clarity before committing your primary change.
* **Rule:** Refactoring is part of development. (This is a mindset, less about specific code examples).
## 10. Optimize for Readability
* **Rule:** Code is read more than written.
* **Don't:** Using overly clever one-liners or complex list comprehensions that are hard to decipher.
```python
# Clever but potentially hard to read
result = [x**2 for x in range(10) if x % 2 == 0 and x > 3]
```
* **Do:** Prioritize clarity, even if it means slightly more verbose code.
```python
result = []
for x in range(10):
is_even = x % 2 == 0
is_greater_than_3 = x > 3
if is_even and is_greater_than_3:
result.append(x**2)
# Or a more readable comprehension if appropriate
result = [x**2 for x in range(4, 10, 2)] # Clearer range
```
## 11. Python-Specific Best Practices
* **Rule:** Embrace Pythonic idioms.
* **Use List Comprehensions (when clear):** Prefer `squares = [x*x for x in numbers]` over manual `for` loop appends for simple transformations.
* **Use Context Managers (`with` statement):** Ensure resources like files or network connections are properly closed.
```python
# Don't
f = open("myfile.txt", "w")
try:
f.write("Hello")
finally:
f.close()
# Do
with open("myfile.txt", "w") as f:
f.write("Hello")
# File is automatically closed here, even if errors occur
```
* **Iterate Directly:** Iterate over sequences directly instead of using index manipulation.
```python
# Don't
for i in range(len(my_list)):
print(my_list[i])
# Do
for item in my_list:
print(item)
# Do (if index is needed)
for i, item in enumerate(my_list):
print(f"Index {i}: {item}")
```
* **Rule:** Use Type Hinting (Python 3.5+). Improves readability, enables static analysis tools (`mypy`), and clarifies intent.
```python
# Don't
def greet(name):
print("Hello " + name)
# Do
def greet(name: str) -> None:
print("Hello " + name)
def add(a: int, b: int) -> int:
return a + b
```
* **Rule:** Use Virtual Environments (`venv`). Isolate project dependencies to avoid conflicts between projects. Always create and activate a virtual environment before installing packages (`pip install ...`).
* **Rule:** Prefer f-strings (Python 3.6+) for string formatting. They are generally more readable and often faster than `.format()` or `%` formatting.
```python
name = "Alice"
age = 30
# Don't (older styles)
print("Name: %s, Age: %d" % (name, age))
print("Name: {}, Age: {}".format(name, age))
# Do
print(f"Name: {name}, Age: {age}")
```
* **Rule:** Understand Mutable Default Arguments. Be wary of using mutable types (like lists or dicts) as default function arguments, as they are shared across calls.
```python
# Don't (potential bug)
def add_item(item, my_list=[]):
my_list.append(item)
return my_list
list1 = add_item(1) # [1]
list2 = add_item(2) # [1, 2] - Unexpected!
# Do
def add_item(item, my_list=None):
if my_list is None:
my_list = []
my_list.append(item)
return my_list
list1 = add_item(1) # [1]
list2 = add_item(2) # [2] - Correct