--- title: "Software Development Standards" date: 2026-10-08T19:18:00+04:00 --- These are the rules I write code by. They are based on Robert Martin's "Clean Code" and PEP 8 for Python. It's a living document, I extend it as I learn things the hard way. The main idea is simple: code is read far more often than it is written. So I write it for the person who opens the file six months from now. Often that person is me. ## 0. No spec, no result Any work starts with requirements, not with code. - First I pin down what problem we're solving and how we'll know it's solved. - Then the architecture: components, data, the boundaries between them. - Only after that, code. If requirements change along the way, I update the spec first, then the code. ## 1. Code style: PEP 8 + my rules PEP 8 is the baseline standard. My rules work on top of it, like the cascade in CSS: everything I haven't overridden is inherited from PEP 8 as is. There's one override. **Tabs instead of spaces.** One level of nesting is one character. Everyone sees indentation at the width they set in their editor, and nothing changes in the file. I never mix spaces and tabs in one file, Python 3 won't allow it anyway. Everything else follows PEP 8: line length, blank lines between functions and classes, import order, spaces around operators. So I don't have to keep this in my head, a tool checks and fixes the style. I use ruff: ```toml # pyproject.toml [tool.ruff] line-length = 100 [tool.ruff.format] indent-style = "tab" [tool.ruff.lint] select = ["E", "F", "I", "N", "B", "UP"] ignore = ["W191"] # W191 complains about tabs, for us it's a deliberate choice ``` IDE: JetBrains (PyCharm). The project settings have tabs enabled and ruff runs on save. ## 2. Names A name should answer three questions: why it exists, what it does and how it's used. If a name needs a comment, it's a bad name. - Variables and functions in `snake_case`, classes in `PascalCase`, constants in `UPPER_CASE`. - Functions are named with a verb: `fetch_page`, `parse_price`, `send_report`. Classes and variables with a noun: `Browser`, `proxy_pool`. - No abbreviations or single-letter names. Exceptions: `i` in a short loop and `e` for an exception. - One concept, one word. If the project has `fetch`, then `get`, `load` and `retrieve` don't show up next to it for the same thing. - No type encoding in names: `users`, not `users_list` or `lst_users`. - Boolean variables read like a question: `is_ready`, `has_proxy`, `can_retry`. ## 3. Functions - **Small.** A function should fit on the screen without scrolling. If it doesn't, it can be split. - **Do one thing.** If a function can't be described in one sentence without the word "and", it's two functions. - **One level of abstraction.** A function either orchestrates a process and calls other functions, or works with details. Not both at once. - **Few arguments.** Ideally zero to two. Three or more is a reason to group them into a dataclass. - **No flag arguments.** `render(page, True)` is hard to read. Better two functions: `render_full` and `render_preview`. - **No hidden side effects.** If a function is called `check_session`, it shouldn't create a new session along the way. - **Command or query.** A function either changes state or returns data. Not both. ## 4. Comments The best comment is the one that wasn't needed because the code is clear as it is. I write a comment when I need to explain **why**, not **what**: ```python # Cloudflare returns 403 on the first request without a cookie, so we always make two response = session.get(url) ``` I don't write: - comments that retell the code - commented-out code. That's what git history is for - a change log and authorship in the file header. That's git's job too A docstring is required for a module's public functions and classes. For internal ones, it depends. ## 5. Error handling - Exceptions instead of return codes. A function doesn't return `None` or `-1` when something goes wrong, it raises an exception. - I catch specific exceptions, never a bare `except:` or `except Exception: pass`. - My own exceptions for my domain: `ProxyBannedError`, `CaptchaError`. They tell you what happened without reading the stack trace. - I don't return `None` where a collection is expected. An empty list is better than a `None` check at every call site. - I handle an error where I know what to do with it. If I don't know, I let it go up. ## 6. Classes and modules - **Small classes with a single responsibility.** A class should have one reason to change. If a class talks to the network, parses HTML and writes to the database, that's three classes. - **High cohesion.** A class's methods work with its fields. If a method doesn't touch `self`, it most likely doesn't belong in this class. - **Dependencies from outside.** A class receives the browser, session or database client in its constructor instead of creating them itself. That makes it easy to test and swap. - **Law of Demeter.** I talk only to immediate neighbors. `order.customer.address.city` is a sign that a method is needed. - **Boundaries with third-party code.** I wrap third-party libraries in my own thin layer. If the library has to be replaced, the change happens in one place. ## 7. Tests - Tests are code too, and the same cleanliness requirements apply. - A test checks one thing, and its name shows it: `test_returns_empty_list_when_page_has_no_items`. - Every test has the same structure: arrange, act, assert. - Tests are fast, independent of each other and give the same result on every run. No trips to the real internet in unit tests. - I reproduce a bug with a test first, then fix it. ## 8. The Boy Scout rule I leave code cleaner than I found it. There's no need to rewrite everything at once. It's enough to rename one unclear variable or extract one piece into a function every time I touch a file. ## 9. Git - One commit, one logical change. - Commit messages in the conventional commits format: `feat:`, `fix:`, `refactor:`, `docs:`, `chore:`. - Code that doesn't pass the linter and tests doesn't get into the main branch. - Secrets, keys and `.env` never get into the repository.