Files
home-information/docs/dev/shared/coding-standards.md
Tony C 47acdaee39 Documentation and agent/command config improvements. (#225)
* Documentation and agent/comment config improvements.
2025-09-23 15:55:17 +00:00

395 lines
17 KiB
Markdown

# Coding Standards
## Code Conventions Checklist
Checklists for writing and reviewing code.
**General**:
- [ ] Code does not use hard-coded "magic" strings.
- [ ] Code does no use any hard-coded "magic" numbers.
- [ ] All comments add value and follow our commenting guidelines.
- [ ] No urls appear as hard-coded path and all use Django url names and `reverse()`.
- [ ] There no .flake8 linting violations.
- [ ] The file ends with a newline
- [ ] All new files follow the project structure's name and location conventions.
- [ ] There are no Emoji's in any code, templates or documentation.
**Imports**:
- [ ] All module imports are at the top of the file.
- [ ] Imports are grouped logically: system/pip, django, project, apps then module local.
- [ ] Imports have one line space between groups.
- [ ] Within a group, imports are sorted alphabetically
- [ ] No module relies on indirect (hidden) imports
- [ ] There are no unused imports
**Method Declarations**:
- [ ] All methods uses type hints for their parameters and return values.
- [ ] All method definition with more than two arguments use one line per argunment.
- [ ] All multi-line method signatures have all their types and default values aligned.
- [ ] No methods return position-dependent tuples.
**Class Declarations**:
- [ ] All dataclass definitions have all their types and default values aligned.
- [ ] All enum definitions have all their types and default values aligned.
- [ ] Are enums subclass LabeledEnum
**Method Calling**:
- [ ] All method calls use named parameters.
- [ ] All metghod calls with more than two argumants use one line per argument.
- [ ] All multi-line method calls use a comma after last item.
- [ ] Spaces surrounding all equals ("=") signs when passing parameters to methods.
**Expressions**:
- [ ] All boolean assignments to conditional clauses are wrapped in `bool()`.
- [ ] All loops end with an explicit `continue` or a `return`.
- [ ] All methods end with an explicit `return` or a `raise`?
- [ ] All multi-line arrays, sets, dictionaries use a comma after last item.
- [ ] Compound/complex conditional statements use explicit delimiting parentheses.
- [ ] Single quote are used for all strings in Python code.
- [ ] All multi-line arrays, dictionaries and sets use a comma after last item.
**Views**:
- [ ] All url paths components follow our standard ordering conventions.
- [ ] All url Django names follow our standard naming conventions
- [ ] All view names match url names (except for casing and underlining).
- [ ] ALl views raise exceptions for common error conditions. (Let middleware handle it.)
**Templates**:
- [ ] Template names referenced in views closely match the view names that use them.
- [ ] No templates have in-line Javascript.
- [ ] No templates have in-line CSS.
- [ ] Templates appear in a subdirectory matching their purpose: modals, panes, pages.
- [ ] Template tags `load` statments near the top of the file.
**Comments**:
- [ ] No comments that state what is obvious from the naming and typing and context.
- [ ] No method docstrings that simply restate what the method name already says.
- [ ] No inline comments that describe what the code is doing (vs why).
- [ ] No comments that refer to the past, future or current work in progress.
## Code Conventions Details
### No "magic" strings
We do not use "magic" or hard-coded strings when needing multiple references. Any string that need to be used in two or more places is a risk of them being mismatched. This includes, but is not limited to:
All DOM ids and class strings that are shared between client and server must adhere to our `DIVID` pattern. See "Client-Server Namespace Sharing" in [Front End Guidelines](../frontend/frontend-guidelines.md).
### Type Hints
- We add type hints to dataclass fields, method parameters and method return values.
- We do not add type hints to locally declared method variables.
- Some allowed, but not required exceptions:
- The `request` parameter when appearing in a Django view class.
- Single parameter methods where the method name or parameter name makes its type unambiguous.
### Method Parameter Formatting
For readability, besides adding type hints to method parameters, we adhere to the following formatting conventions:
- For methods with a single parameter, or parameters of native types, they can appear in one line with the method name.
- If more than one parameter and app-defined types, then use a multiple line declaration.
- For methods with three or more parameters, we use one line per parameter and align the type names.
**Good Examples**:
```
def set_entity( self, entity_id : int ) -> EntityPath:
def set_entity_order( self, entity_id : int, rank : int ) -> EntityPath:
def set_entity_path( self,
entity_id : int,
location : Location,
svg_path_str : str ) -> EntityPath:
```
**Bad Examples**:
```
def set_entity_type( self, entity_id : int, entity_type : EntityType ) -> EntityPath:
def set_entity_path( self,
entity_id : int,
location : Location,
svg_path_str: str ) -> EntityPath:
def set_entity_path( self, entity_id : int,
location : Location, svg_path_str: str ) -> EntityPath:
```
### Variable Assignment vs Inlining
We prefer explicit variable assignment over inlining function calls. This is not about minimizing lines of code - it's about readability and debuggability.
**Good** - Named intermediate values
```python
table_name = self.queryset.model._meta.db_table
logger.debug( f"Processing table: {table_name}" )
cutoff_date = datetimeproxy.now() - timedelta( days=30 )
old_records = queryset.filter( created__lt=cutoff_date )
```
**Bad** - Inlined function calls
```python
logger.debug( f"Processing table: {self.queryset.model._meta.db_table}" )
old_records = queryset.filter(
created__lt=datetimeproxy.now() - timedelta( days=30 )
)
```
Benefits of variable assignment:
- Provides semantic naming that clarifies intent
- Easier to debug (can inspect intermediate values)
- Improves readability by breaking complex expressions
- Allows reuse without recalculation
### Explicit Booleans
We prefer to wrap all expression that evaluate to a boolean in `bool()` to make it explicit what type we are expecting:
**Good**
```
my_variable = bool( len(my_list) > 4 )
```
**Bad***
```
my_variable = len(my_list) == 4
```
### Complex Boolean Expressions
- For boolean clauses and conditionals where there are multiple clauses, we prefer to explicitly enclose each clause with parentheses in order to make the intention clear.
- We do not rely on the user having a deep understanding of the compiler's ordeer of precedence.
- We use one line per clause unless the combined clauses are very short and obvious.
- Single boolean typed variables or methods that return a boolean do not need paretheses.
**Good**:
```
if is_editing and location_view:
pass
if (( hass_state.domain == HassApi.SWITCH_DOMAIN )
and ( HassApi.LIGHT_DOMAIN in prefixes_seen )):
pass
if ( HassApi.BINARY_SENSOR_DOMAIN in domain_set
and device_class_set.intersection( HassApi.OPEN_CLOSE_DEVICE_CLASS_SET )):
pass
```
**Bad**:
```
if hass_state.domain == HassApi.SWITCH_DOMAIN and HassApi.LIGHT_DOMAIN == 'foo':
pass
```
### Control Flow Statements
- Always include explicit `continue` statements in loops
- Always include explicit `return` statements in functions
- This improves code readability and makes control flow intentions explicit
Example:
```python
def process_items(items):
results = []
for item in items:
if not item.valid:
continue # Explicit continue for invalid items
if item.needs_processing:
result = process(item)
results.append(result)
continue # Explicit continue after processing
# Handle non-processing case
results.append(item.default_value)
continue # Explicit continue at end of loop
return results # Explicit return at end of function
```
### Operator Spacing
- Use spaces around assignment operators and most other operators in expressions
- Examples: `x = y + z`, `result += value`, `if count == 0`
- Exception: Don't add spaces in function keyword arguments (`func(x=y)`) or type annotations
### Parentheses Spacing (Deliberate PEP8 Deviation)
- **We prefer spaces inside parentheses for enhanced readability**
- This is a deliberate deviation from PEP8 standards (E201, E202)
- Examples:
- Good: `if ( condition ):`
- Good: `my_function( param1, param2 )`
- Good: `result = calculate( x + y )`
- This applies to all parentheses: function calls, conditionals, expressions
- Rationale: Extra spacing improves readability by visually separating content from delimiters
### Boolean Expressions
When assigning or returning boolean values, wrap expressions in `bool()` to make intent explicit:
```python
# Good - explicit boolean conversion
is_active = bool(user.last_login)
in_modal_context = bool(request.POST.get('context') == 'modal')
# Avoid - implicit boolean conversion
is_active = user.last_login
in_modal_context = request.POST.get('context') == 'modal'
```
### Linting: Flake8 Configurations
The project uses two different flake8 configurations:
- Development Configuration (`src/.flake8`) : Our preferred style for daily development work, with specific whitespace deviations from PEP8 for enhanced readability:
- **E201, E202**: We use spaces inside parentheses for better visual separation
- **E221**: We align operators and values in multi-line declarations
- **E251**: We use spaces around keyword parameters for consistency
- **Note**: These are deliberate choices for improved code readability, not oversights
- CI Configuration (`src/.flake8-ci`): GitHub Actions enforces these standards and blocks PR merging if violations exist.
## Commenting Guidelines
- We avoid over-commenting and let the code variable/method naming be clear on the intent.
- We believe in the philosophy: "Before add a comment, ask yourself how the code can be changed to make this comment unnecessary."
- Do not add comments that are not timeless and refer to work in progress or future work. i.e., it must make sense for future readers of the code.
- Comments should explain the **why** not the **what**. Good comments document:
- Non-obvious design decisions
- Complex business logic
- External API/library quirks and workarounds
- Time-based complexities
- Bug fixes that prevent regression
- Avoid comments that:
- State what the code obviously does
- Contain development artifacts or work-stream context
- Leave commented-out code (creates confusion)
- Explain what better naming could clarify
- When in doubt, ask: "Can I change the code to make this comment unnecessary?"
### Special Cases
#### TRACE Pattern (Accepted)
- `TRACE = False # for debugging` is an accepted pattern
- Addresses Python logging limitation (lacks TRACE level below DEBUG)
### Examples
#### GOOD Comments - What TO Include
1. **Design rationale that is non-obvious**
- Example: `# Single source of truth for position vs path classification`
- Explains architectural decisions
2. **Complex domain logic explanations**
- Example: Multi-line explanation of entity delegation concept
- Business rules that aren't obvious from code structure
3. **Summarize complex or non-standard coding approaches**
- When using unusual patterns or workarounds
- Algorithm explanations that aren't obvious
4. **Design decision rationale**
- Example: "The general types could be used for these, since all are just name-value pairs. However, by being more specific, we can provide more specific visual and processing"
- Explains why one approach was chosen over alternatives
5. **Mathematical/geometric calculations**
- Example: `'80.0,40.0', # top-left (100-20, 50-10)`
- Coordinate calculations that are difficult to verify mentally
- Especially valuable in test cases for validation
6. **Cross-file coordination notes**
- Example: `# Match SvgItemFactory.NEW_PATH_RADIUS_PERCENT`
- Important synchronization between related constants/values in different files
7. **Complex domain abstractions**
- Example: Multi-line explanation of LocationItem interface concept
- Abstract concepts that need implementation guidance
8. **Multi-step process/algorithm documentation**
- Example: `alert_manager.py:40-56` - Breaks down the three distinct checks and explains why HTML is always returned
- Complex workflows that need step-by-step explanation of the "why"
9. **External API/library limitations and workarounds**
- Example: `wmo_units.py:838-841` - Documents Pint library limitations requiring unit mappings
- Example: `zoneminder/monitors.py:81-87` - pyzm timezone parsing quirks
- Critical for understanding why non-obvious code patterns exist
- Brief expressions of frustration (e.g., "ugh") acceptable when documenting known pain points
10. **External service configuration rationale**
- Example: `usno.py:59-62` - Documents API priority, rate limits, polling intervals
- Explains constraints and decisions for external integrations
11. **Future extension points**
- Example: `daily_weather_tracker.py:96-100` - "Future: Add other weather field tracking here"
- Marks logical insertion points for anticipated features
- Should be brief hints, not commented-out code blocks
12. **Temporal/timing complexity in APIs**
- Example: `zoneminder/monitors.py:99-110` - Events as intervals vs points, open/closed handling
- Example: `zoneminder/monitors.py:166-171` - Why polling time cannot advance
- Critical for understanding time-based edge cases in external systems
13. **Bug fix documentation**
- Example: `zoneminder/monitors.py:132-133` - "This fixes the core bug where..."
- Documents what was broken and why the current approach fixes it
- Helps prevent regression
#### BAD Comments - What NOT To Include
1. **Method docstrings that restate the obvious**
- Bad: `"""Get the total number of records in the history table."""` for `_get_record_count()` in a HistoryTableManager class
- Bad: `"""Delete records with the given IDs."""` for `_delete_records( ids )`
- Bad: `"""Types of cleanup operation results."""` for an enum called CleanupResultType
- The method/class name + parameters + return type already convey this information
- Exception: Public API methods may need docstrings for documentation generation
2. **Avoid commenting obvious variable purposes**
- Bad: `# Store original entity_type_str to detect changes`
- Bad: `# Human-readable message for health status` when field is named `reason: str`
- The variable name and type should make this clear
3. **Remove work-stream artifacts**
- Bad: Comments explaining why tests were removed or referencing specific issues
- Comments should be timeless, not tied to particular development contexts
4. **Redundant descriptions of clear code**
- Bad: `# Track EntityType change and response needed after transaction`
- Bad: `# Check if we're over the total record limit` before `if total_count <= self.max_records_limit:`
- When variable names and conditionals already convey this information
5. **Cryptic references**
- Bad: `# Recreate to preserve "max" to show new form`
- If unclear, either explain properly or remove
5. **Development phase/work-stream artifacts**
- Bad: Comments explaining "Phase 3/Phase 4" development contexts
- Bad: Explanations of why code was removed or changed
- These belong in commit messages or PR descriptions, not in main branch code
6. **TODO comments (high bar to justify)**
- Generally avoid TODOs in main branch
- If important enough for TODO, create an issue and prioritize it
- Only acceptable when there's a compelling reason not to address immediately
- Must be specific and actionable, not vague intentions
7. **Commented-out code**
- Bad: Dead code that's been disabled or replaced
- Bad: Example implementations as large code blocks
- Bad: Logic that looks like it might need uncommenting (e.g., `zoneminder/integration.py:65-66`)
- If needed for future reference, create an issue instead
- Exception: Very brief one-line hints at extension points (see "Future extension points" in good comments)
- Commented code creates confusion about whether it should be active
## Related Documentation
- Testing standards: [Testing Guidelines](../testing/testing-guidelines.md)
- Backend patterns: [Backend Guidelines](../backend/backend-guidelines.md)
- Frontend standards: [Frontend Guidelines](../frontend/frontend-guidelines.md)
- Workflow and commits: [Workflow Guidelines](../workflow/workflow-guidelines.md)