{"slug": "not-every-lint-warning-is-cosmetic", "title": "Not Every Lint Warning Is Cosmetic", "summary": "A developer working on Python backend cleanup tasks with pylint, flake8, and mypy discovered that seemingly cosmetic lint warnings often reveal hidden contracts in the code. Four cases—enum naming, function signatures, module size, and documentation—showed that fixing warnings led to explicit persistence semantics, ownership checks, clearer module boundaries, and proper documentation. The developer argues that lint warnings serve as early signals for boundary and invariant checks, not just style improvements.", "body_md": "How old tools improve the work of new (non)humans.\n\nI noticed this pattern while working through a series of backend cleanup tasks in `pylint`\n\n, `flake8`\n\n, and `mypy`\n\n: some warnings that I wanted to dismiss as housekeeping kept turning out to be the shortest path to hidden contracts in the code.\n\nAs a rule, they looked like small cleanup tasks: naming style, function signatures, module size. But once I started fixing them, it became clear that the problem was not cosmetic. The check was simply the first thing pointing at a place where an important contract in the code was still resting on an unspoken assumption.\n\nFor Python backend development, this is especially noticeable in places where the code already looks plausible and locally reasonable — including cases where the draft was assembled quickly with the help of an LLM. In those places, a warning is sometimes useful not as a demand to “make it cleaner”, but as an early signal: this is where it is worth checking boundaries, invariants, or the shape of the contract.\n\nBelow are four short cases where cleanup turned out to be not quite cleanup.\n\nAt first this looked almost cosmetic: I was simply normalizing naming style.\n\n```\nclass Operator(str, Enum):\n    eq = \"=\"\n    lt = \"<\"\n```\n\nthen:\n\n```\nclass Operator(str, Enum):\n    EQ = \"=\"\n    LT = \"<\"\n```\n\nBut that was not the end of it. It turned out that the real contract lives not only in the Python enum, but also in which values SQLAlchemy reads from and writes to the database. So the real fix ended up looking like this:\n\n```\noperator: MappedColumn[Operator] = mapped_column(\n    SQLEnum(Operator, name=\"operator_enum\", values_callable=_enum_lower_names)\n)\n```\n\nSo the warning was not really about enum style. It was about persistence semantics. From the outside it looked like cleanup, but in practice it forced me to make the contract between the Python enum and stored values explicit.\n\nThe next signal looked even more mundane: an extra parameter and a messy signature.\n\n``` python\nasync def delete_condition(db: AsyncSession, item_id: UUID, condition_id: UUID) -> None:\n    item = await db.get(ChecklistItem, item_id)\n```\n\nWhen I dug into it, it turned out the warning was not really about the shape of the function. It pointed at an under-specified domain contract. After the fix, the function explicitly required the `item_id`\n\nand `checklist_id`\n\npair:\n\n``` python\nasync def delete_condition(\n    db: AsyncSession,\n    checklist_id: UUID,\n    item_id: UUID,\n    condition_id: UUID,\n) -> None:\n    _ = await get_draft_item(db=db, item_id=item_id, checklist_id=checklist_id)\n```\n\nWhat mattered here was not the signal about the signature itself, but the fact that it led to an ownership check. After the fix, what became explicit was not the “neatness” of the function, but the dependency between `item_id`\n\nand `checklist_id`\n\n.\n\nThe third case was about structure. A warning about module size and shape would have been easy to dismiss as a linting nitpick. But the monolithic `schemas.py`\n\nhad in fact stopped being maintainable.\n\n```\n# app/schemas.py\nclass HTTPErrorDetail(BaseModel): ...\nclass AuditEvent(BaseModel): ...\nclass ChecklistItemCreate(BaseModel): ...\nclass OrganisationCreate(BaseModel): ...\nclass TemplateListItem(BaseModel): ...\n```\n\n...and so on for hundreds of lines.\n\nInstead of adding a suppress, this ended up as a proper package:\n\n``` python\n# app/schemas/__init__.py\nfrom .audit import AuditEvent\nfrom .checklists import Checklist, ChecklistItem, ItemCondition\nfrom .enums import Operator, PropName\nfrom .orgs import Organisation, Invite, Grant\nfrom .templates import TemplateListItem, TemplatePublic\n```\n\nSo the problem was not style as such. It was that the codebase was already asking for clearer boundaries. In this case, the file-size warning was not noise, but an early sign that the module needed to be split along responsibilities.\n\nAnother case initially looked purely documentation-related. `pylint`\n\nand `flake8`\n\nrequired proper docstrings for functions, methods, and classes, and from the outside this is easy to read as hygiene for the sake of hygiene.\n\nBut in several places the fix worked differently. For example, in `checklist_router.py`\n\nthe docstring stopped being a generic phrase about a “router for Checklist entity” and turned into a short description of the real lifecycle contract: that published versions are immutable, that draft creation is explicit, that editor mutations operate only on the draft layer, and which denial/error semantics are considered normal here.\n\nA similar thing happened with middleware. There the docstring started fixing not a paraphrase of the method, but important boundary conditions: when `Session-Log-ID`\n\nis required, why the middleware returns `400`\n\n, where exactly the request-scoped DB session lives, and why this is ASGI middleware rather than `BaseHTTPMiddleware`\n\n.\n\nThe framing from [Arun Rajkumar’s post about agent-driven workflow](https://dev.to/mickyarun/i-replaced-scrum-jira-and-our-wiki-with-12-ai-agents-on-a-mac-mini-o7o) is also useful here, because in that framing code and related artifacts act as a source of working context. Read that way, a meaningful docstring is not decorative documentation, but another explicit form of a local contract, useful not only to a human, but also to an AI agent: it no longer has to reconstruct those constraints from the implementation every single time.\n\nOf course, not every docstring has that effect. If a checker only pushes on form — imperative mood, blank lines, section headers — that is more a matter of formatting discipline. That is still useful at least as a factor of consistency, but it is a different kind of value. But where a docstring fixes preconditions, boundaries, error semantics, or lifecycle assumptions, a documentation warning stops being pure cosmetics.\n\nAcross all four cases, what matters is not the warning itself as a ritual, but the move from implicit to explicit. In one place I had to make persistence semantics explicit, in another an ownership boundary, in the third module boundaries, and in the fourth a local API or middleware contract.\n\nThat is probably the most useful way to read such signals. Not as an order to make the code neater, but as a reason to check whether an important contract is still resting on a silent “this is obvious anyway.” This does not mean every warning hides a deep problem. But if it touches storage format, ownership, input shape, or a module boundary, I would no longer treat it as pure cosmetics.", "url": "https://wpnews.pro/news/not-every-lint-warning-is-cosmetic", "canonical_source": "https://dev.to/s_a_shkuratov/not-every-lint-warning-is-cosmetic-11f8", "published_at": "2026-06-14 08:32:54+00:00", "updated_at": "2026-06-14 08:58:54.889000+00:00", "lang": "en", "topics": ["developer-tools", "machine-learning", "large-language-models"], "entities": ["pylint", "flake8", "mypy", "SQLAlchemy", "Python"], "alternates": {"html": "https://wpnews.pro/news/not-every-lint-warning-is-cosmetic", "markdown": "https://wpnews.pro/news/not-every-lint-warning-is-cosmetic.md", "text": "https://wpnews.pro/news/not-every-lint-warning-is-cosmetic.txt", "jsonld": "https://wpnews.pro/news/not-every-lint-warning-is-cosmetic.jsonld"}}