DEV Community

Sergey Shkuratov
Sergey Shkuratov

Posted on

Not Every Lint Warning Is Cosmetic

How old tools improve the work of new (non)humans.

I noticed this pattern while working through a series of backend cleanup tasks in pylint, flake8, and mypy: some warnings that I wanted to dismiss as housekeeping kept turning out to be the shortest path to hidden contracts in the code.

As 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.

For 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.

Below are four short cases where cleanup turned out to be not quite cleanup.

1. Enum cleanup that exposed a database contract

At first this looked almost cosmetic: I was simply normalizing naming style.

class Operator(str, Enum):
    eq = "="
    lt = "<"
Enter fullscreen mode Exit fullscreen mode

then:

class Operator(str, Enum):
    EQ = "="
    LT = "<"
Enter fullscreen mode Exit fullscreen mode

But 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:

operator: MappedColumn[Operator] = mapped_column(
    SQLEnum(Operator, name="operator_enum", values_callable=_enum_lower_names)
)
Enter fullscreen mode Exit fullscreen mode

So 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.

2. An extra parameter that turned out to be an ownership check

The next signal looked even more mundane: an extra parameter and a messy signature.

async def delete_condition(db: AsyncSession, item_id: UUID, condition_id: UUID) -> None:
    item = await db.get(ChecklistItem, item_id)
Enter fullscreen mode Exit fullscreen mode

When 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 and checklist_id pair:

async def delete_condition(
    db: AsyncSession,
    checklist_id: UUID,
    item_id: UUID,
    condition_id: UUID,
) -> None:
    _ = await get_draft_item(db=db, item_id=item_id, checklist_id=checklist_id)
Enter fullscreen mode Exit fullscreen mode

What 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 and checklist_id.

3. A module warning that turned out to be about boundaries

The 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 had in fact stopped being maintainable.

# app/schemas.py
class HTTPErrorDetail(BaseModel): ...
class AuditEvent(BaseModel): ...
class ChecklistItemCreate(BaseModel): ...
class OrganisationCreate(BaseModel): ...
class TemplateListItem(BaseModel): ...
Enter fullscreen mode Exit fullscreen mode

...and so on for hundreds of lines.

Instead of adding a suppress, this ended up as a proper package:

# app/schemas/__init__.py
from .audit import AuditEvent
from .checklists import Checklist, ChecklistItem, ItemCondition
from .enums import Operator, PropName
from .orgs import Organisation, Invite, Grant
from .templates import TemplateListItem, TemplatePublic
Enter fullscreen mode Exit fullscreen mode

So 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.

4. Docstrings that turned out to hold a local contract

Another case initially looked purely documentation-related. pylint and flake8 required proper docstrings for functions, methods, and classes, and from the outside this is easy to read as hygiene for the sake of hygiene.

But in several places the fix worked differently. For example, in checklist_router.py the 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.

A similar thing happened with middleware. There the docstring started fixing not a paraphrase of the method, but important boundary conditions: when Session-Log-ID is required, why the middleware returns 400, where exactly the request-scoped DB session lives, and why this is ASGI middleware rather than BaseHTTPMiddleware.

The framing from Arun Rajkumar’s post about agent-driven workflow 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.

Of 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.

What follows from this

Across 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.

That 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.

Top comments (0)