Python Anti-Slop SKILL.md A developer published a Python coding guideline, "anti-slop-python," that instructs engineers to strip defensive over-engineering from Python codebases. The rules target untyped dictionaries, generic wrapper helpers like to_string and as_dict, broad Any annotations, and blanket exception handling, arguing that validation should happen at system boundaries while internal logic relies on precise types and concrete library exceptions such as DuplicateKeyError. | name | anti-slop-python | |---|---| | description | Simplify Python code by removing defensive over-engineering, unnecessary abstractions, generic dictionaries, excessive runtime checks, wrapper helpers, and AI-generated architectural noise. | Apply these rules whenever modifying Python code. The goal is simple, explicit, typed, idiomatic Python that is easy to trace and does not defend against impossible internal states. The default philosophy is: Validate untrusted data at the boundary. Use precise types and straightforward Python everywhere else. Prefer boring code over clever abstractions. Search aggressively for: Any dict str, Any Mapping str, Any list Any object inside normal application logic. If the shape is known, define it. Bad: php def get domain data: dict str, Any - str: value = data.get "domain" if not isinstance value, str : return "" return value Prefer: @dataclass class DomainEvent: domain: str Then: event.domain Or use the project's existing model system: class DomainEvent BaseModel : domain: str Do not carry untyped dictionaries deep into the application. Be suspicious of functions such as: as string to string safe string string value to int as int safe int to bool to dict as dict ensure dict normalize value Bad: php def to string value: Any - str: if value is None: return "" if isinstance value, str : return value if isinstance value, int : return str value if isinstance value, ObjectId : return str value return repr value Ask: What is this value actually supposed to be? If it is a string: php def normalize domain domain: str - str: return domain.strip .lower Do not make every function capable of accepting arbitrary Python values. Bad: php def normalize domain value: Any - str: if isinstance value, str : return value.strip .lower if isinstance value, int : return str value .strip .lower return "" A domain name should not randomly be an integer. Prefer: php def normalize domain domain: str - str: return domain.strip .lower Types should describe valid application states. Do not broaden inputs solely to make code more "defensive." Bad: php def as project value: Any - Project: if not isinstance value, dict : return {} return cast Project, value This does not validate Project . Either validate at the boundary using the project's real validation mechanism, or trust the value at a known integration point. Do not write several runtime checks and still finish with cast . Do not treat every cast as inherently bad. Bad cleanup: if isinstance error, Exception and hasattr error, "code" and isinstance error.code, int and error.code == DUPLICATE KEY ERROR CODE : ... when this is enough at a known driver boundary: return cast Any, error .code == DUPLICATE KEY ERROR CODE Better still, use the driver's actual exception type if available: except DuplicateKeyError: ... Do not turn one simple, understood assumption into five branches merely to avoid a cast. Do not manually inspect generic exceptions when the library exposes a concrete exception. Bad: except Exception as exc: if getattr exc, "code", None == 11000: ... Prefer: except DuplicateKeyError: ... Use exception APIs provided by the dependency. Do not reinvent error classification. Be suspicious of: try: ... except Exception: return None or: try: ... except Exception: pass or: try: ... except Exception as exc: logger.error exc return {} Catch exceptions you can actually handle. Do not silently convert unexpected programming errors into empty values. Avoid: try: ... except: pass except at extremely deliberate process-level boundaries. Bare except also catches system-exiting exceptions. Use specific exception types. Bad: try: return await service.run except Exception: raise Delete the try/except . Likewise: try: ... except Exception as exc: raise exc is usually worse than letting the exception propagate naturally. Bad: try: await operation except Exception: return None If failure is acceptable, make that business rule explicit. If it is not acceptable, let the error propagate. Silent failure is not robustness. Bad: try: return repository.get project project id except Exception: logger.exception "Failed to get project" raise if an outer handler also logs the same error. Prefer returning/raising errors through internal layers and logging once at the responsible boundary. Avoid duplicate stack traces. Search for suspicious patterns like: return "" return {} return return None value or "" value or {} value or data.get "field", "" data.get "field", {} when the fallback hides an invalid state. Bad: project id = data.get "project id", "" if project id is required. Prefer validation at the boundary and then: project id = data.project id Do not hide missing required values. Bad: name = project.get "name" region = project.get "region" config = project.get "config", {} when the schema guarantees those keys. If it is a typed dictionary: class ProjectData TypedDict : name: str region: str config: ProjectConfig prefer: project "name" project "region" project "config" Or better, use a model/dataclass where appropriate. Use .get when absence is actually valid. Bad: domain = data.get "project", {} .get "config", {} .get "domain", {} .get "name" This usually hides malformed input. Prefer modeling the structure properly. data.project.config.domain.name or validated dictionary access. Do not turn invalid nested structures into None silently. Be suspicious of: dict str, Any Mapping str, Any MutableMapping str, Any moving between: handler → service → manager → repository → processor If the schema is known, define a model. Possible tools include: - dataclasses - TypedDict - Pydantic models - attrs - domain classes Use whichever the project already uses. Do not introduce another modeling framework unnecessarily. Good: class QueuePayload TypedDict : event: str data: dict str, Any can be appropriate at a JSON-shaped boundary. But do not turn the entire domain model into nested TypedDicts if actual objects would make business logic clearer. Use the simplest representation that fits the project. Dataclasses are useful when a plain object represents structured internal data. Good: @dataclass frozen=True class DomainMapEvent: project id: str domain: str Avoid adding: post init classmethod factories builder methods conversion methods validation methods unless they enforce meaningful invariants. A dataclass should not become a mini-framework. Pydantic is excellent at boundaries. It does not need to become the base representation for every internal object. Use it where validation/parsing is valuable: - HTTP requests - environment/config - external payloads - queue events - API responses Inside trusted application code, plain dataclasses or typed objects may be simpler. Follow the codebase's existing style. Bad: request = ProjectRequest.model validate payload ... service.create project request ... if not request.project id: raise ValueError ... ... if not isinstance request.project id, str : ... If the boundary model already validated it, trust it. Do not revalidate the same object at every layer. Bad: if not isinstance project.id, str : return None when: @dataclass class Project: id: str already guarantees it. Trust internal types. If the type is inaccurate, fix the type. Be suspicious of: if hasattr value, "id" : ... or: domain = getattr document, "domain", None when the object has a known type. Prefer: document.domain Use getattr for truly dynamic APIs, not as general defensive programming. Bad: project id = getattr project, "id", "" if project.id is required. Prefer: project.id If the object may genuinely be absent: if project is None: raise ProjectNotFound ... Then continue normally. Be suspicious of: getattr setattr hasattr vars dict inspect dir inside ordinary business logic. Python is dynamic, but that does not mean application code should discover its own shape at runtime. Use explicit attributes and types. If a feature relies heavily on: inspect.signature inspect.getmembers inspect.isclass ask whether the code is building infrastructure/framework behavior or simply avoiding explicit APIs. Reflection is appropriate in frameworks/tooling. It is suspicious in normal domain logic. Bad: php def serialize value value: Any - str: if isinstance value, str : return value if isinstance value, int : return str value if isinstance value, ObjectId : return str value if isinstance value, datetime : return value.isoformat return repr value unless arbitrary-value serialization is genuinely the feature. If you know the field type, serialize that type directly. Be suspicious of: domain id from project id from extract id extract name resolve field safe field object id from string from Bad: php def domain id from document: dict str, Any | None - str | None: if document is None: return None domain = document.get "domain" if not isinstance domain, ObjectId : return None return str domain If the document contract is known: @dataclass class DNSDocument: domain: ObjectId then: str document.domain Fix typing rather than introducing another helper. Be suspicious of functions that: - have one caller - are one to three lines - only access a property - only call .strip - only call .lower - only perform isinstance - only return a fallback - only forward parameters - only rename another function Bad: php def get project id project: Project - str: return project.id Prefer: project.id Helpers should represent real concepts. Do not split simple logic into: parser.py normalizer.py validator.py converter.py mapper.py resolver.py helper.py processor.py manager.py for a tiny feature. Keep related code together when it improves readability. Separation of concerns does not mean separation of every statement. Be suspicious of: Controller → Service → Manager → Processor → Handler → Repository → DAO when most layers simply forward parameters. Python does not need enterprise ceremony. Collapse layers that add no logic. Bad: php class ProjectManager: def get project self, project id: str - Project: return self.project service.get project project id and then: php class ProjectService: def get project self, project id: str - Project: return self.repository.get project project id If a layer adds no policy, transformation, caching, orchestration, or meaningful abstraction, remove it. Be suspicious of: class BaseService: ... class BaseRepository: ... class AbstractManager: ... when subclasses share little meaningful behavior. Do not create inheritance hierarchies just to centralize two utility methods. Prefer composition or direct code. Bad: class MongoProjectRepository BaseRepository, LoggingMixin, RetryMixin : ... when dependencies/functions can be explicit. Multiple inheritance and mixin stacks make behavior hard to trace. Use them only when they genuinely simplify the architecture. Be suspicious of: LoggingMixin ValidationMixin SerializationMixin RetryMixin TimestampMixin ErrorHandlingMixin for ordinary application classes. Mixins often hide dependencies and control flow. Prefer explicit calls or composition. Do not create: class ProjectRepository Protocol : ... class AbstractProjectRepository ABC : ... when there is one concrete implementation and no real abstraction need. Protocols/interfaces are useful for narrow consumer contracts and interchangeable implementations. Do not add them because "good architecture requires interfaces." Bad: python class ProjectGetter Protocol : def get project ... : ... created solely because one test needs a mock. Python's testing ecosystem already supports dependency substitution easily. Use protocols when they express a meaningful contract. Bad: php class RepositoryFactory: def create self, type : str - Repository: ... when the application always uses one repository. Prefer direct construction. Factories should solve actual runtime selection or complex setup. Bad: deployment = DeploymentBuilder .with project id project id .with region region .with port port .build Prefer: deployment = Deployment project id=project id, region=region, port=port, Python already has excellent object construction syntax. Do not emulate Java builders unnecessarily. Be suspicious of: Project.from dict ... Project.from payload ... Project.from model ... Project.from entity ... Project.from record ... when the transformations are trivial or duplicate one another. Use alternative constructors only when they express genuinely different construction logic. Audit: to dict from dict to model from model to dto from dto to entity from entity to schema from schema If two representations are nearly identical, question why both exist. Do not maintain fleets of copy-field transformations without a real boundary distinction. Be suspicious of: Project ProjectDTO ProjectData ProjectPayload ProjectRequest ProjectResponse ProjectModel ProjectEntity ProjectRecord with almost the same fields. Separate models where API/persistence/domain contracts genuinely differ. Do not duplicate types just because each layer supposedly needs its own model. Python dependency injection can simply be: service = ProjectService repository, logger Do not introduce: Container Registry Provider Resolver ServiceLocator DependencyGraph without a real need. Explicit construction is easier to trace. Bad: repo = services.get "project repository" Prefer explicit dependencies. Likewise, avoid mutable module-level globals for application services/config where explicit wiring is practical. Bad: class DatabaseSingleton: instance = None @classmethod def get instance cls : ... unless the lifecycle genuinely requires it. Usually the application startup layer can create one instance and pass it around. Do not add wrapper utilities around normal context manager behavior without value. Bad: python def safe transaction db : return TransactionContext db when: with db.transaction : ... already expresses the operation clearly. Do use them for real resources: with open path as file: async with client.stream ... as response: with transaction: Do not replace appropriate resource management merely to reduce lines. Be suspicious of: python @retry ... def everything : Retries are not generic safety. Only retry operations that are: - transient - idempotent or safe to repeat - appropriate for retry semantics Do not add retries around arbitrary business logic. Be suspicious when functions accumulate: @retry @log execution @validate @measure @catch errors @authorize @normalize Decorators hide control flow. Use them for genuinely cross-cutting concerns with stable semantics. Do not turn basic logic into a decoration stack. Bad: python @ensure not none def process project ... : when: if project is None: raise ProjectNotFound ... is clearer. Explicit logic is often better than decorator magic. Bad: if project is not None: if project.enabled: if project.status == "active": 50 lines Prefer: if project is None: raise ProjectNotFound project id if not project.enabled: return if project.status = "active": return main logic Keep the happy path obvious. Bad: if enabled is True: when: if enabled: is equivalent. Bad: if enabled == False: Prefer: if not enabled: Use explicit is True only when tri-state behavior genuinely matters. Bad: value = "a" if active else "b" if enabled else "c" Prefer normal control flow. Do not compress logic at the expense of readability. Bad: raw domain = event.domain trimmed domain = raw domain.strip normalized domain = trimmed domain.lower domain = normalized domain Prefer: domain = event.domain.strip .lower Use intermediate names only when they clarify meaningful concepts. Be suspicious of: return list items or: copy = items : when ownership/mutation does not require a copy. Do not allocate defensively without a real reason. Likewise: return dict config should have a concrete ownership reason. Do not copy mutable structures mechanically. Search for: copy.deepcopy ... Deep copying can be expensive and usually signals unclear ownership. Use it only when nested mutation isolation is actually required. Do not add: @lru cache @cache to functions without understanding: - lifecycle - cardinality - invalidation - memory growth - stale data behavior Caching is architecture, not a free optimization. Do not make functions async just because surrounding code is async. Bad: php async def normalize domain domain: str - str: return domain.strip .lower Prefer synchronous functions for synchronous work. Be suspicious of: asyncio.create task ... added simply to "not block." Every background task raises questions about: - ownership - cancellation - exception handling - shutdown - ordering - lifetime Use task creation deliberately. Bad: asyncio.create task send event with no task tracking or error handling. If the result matters, await it. If fire-and-forget is intentional, ensure the application owns the task lifecycle. Do not turn two trivial sequential calls into concurrency automatically. Use concurrent execution when operations are independent and actually benefit from overlapping I/O. Do not make control flow harder for theoretical speedups. Avoid: asyncio.Lock threading.Lock without real shared mutable state and concurrency. Locks create lifecycle and deadlock complexity. Protect actual races, not hypothetical ones. Do not wrap already-async libraries in: asyncio.to thread ... run in executor ... without need. Use thread offloading for genuinely blocking operations. Do not introduce workers/process pools for small CPU work without evidence. Measure first. Simple code first. Be suspicious of: @dataclass class Result Generic T : value: T | None error: Exception | None success: bool Python already has exceptions. Do not emulate Rust/Go-style result handling unless the codebase deliberately uses that model. Question: str | None Project | None Config | None when the value is actually required after construction. Do not model required internal state as nullable purely because data initially enters incompletely. Parse/build a valid object first. Bad: if project is None: return None if project.config is None: return None if project.config.domain is None: return None when the object contract says these are required. Fix the model. Use None only for real optionality. Bad: port = value or 8080 when 0 might have meaning. Bad: enabled = value or True Use explicit None handling when appropriate: port = 8080 if value is None else value Do not conflate falsey with missing. Be suspicious of: str value int value bool value float value used as "validation." For example: bool "false" is True . Do not silently coerce malformed external data. Parse it according to its real contract. Good: class DeploymentStatus StrEnum : PENDING = "pending" RUNNING = "running" FAILED = "failed" when the valid states are closed and domain-significant. Do not create an enum for every arbitrary string. Be suspicious of: @dataclass class ProjectID: value: str when a string is sufficient. A custom type can be useful when it provides real validation or domain behavior. Do not wrap every primitive. Bad: python class ProjectList: def init self, projects: list Project : self. projects = projects with methods that merely proxy list behavior. Use built-in collections unless a domain abstraction provides real value. Before keeping custom helpers, check whether Python already has the operation. Prefer: str.strip str.lower pathlib.Path collections.defaultdict itertools functools dataclasses enum contextlib urllib.parse where appropriate. Do not maintain custom versions of standard behavior. Audit modules/packages called: utils.py helpers.py common.py shared.py misc.py base.py core.py These often collect unrelated functions. Delete trivial helpers. Move domain-specific logic to the domain that owns it. Do not create another generic utility module during cleanup. Do not create a new file for every class/function. Bad: domain parser.py domain normalizer.py domain validator.py domain converter.py for four tiny functions. Group cohesive functionality. File count is not architecture quality. A module can contain several closely related functions. Do not interpret "single responsibility" as "one function per file." Optimize for discoverability. Be suspicious of: HANDLERS = {} register handler ... register service ... PLUGIN REGISTRY = {} when a normal match /dictionary literal/import is sufficient. Use dynamic registration only when extensibility is genuinely required. Bad: handler = registry.resolve event.type return handler.process event when: match event.type: case "insert": return handle insert event case "delete": return handle delete event is clearer. Do not turn static cases into plugin architecture. Three branches do not automatically need: BaseStrategy InsertStrategy DeleteStrategy ReplaceStrategy StrategyFactory Use ordinary Python control flow when easier to understand. Bad: processor value, lambda x: normalize x when: processor value, normalize is enough. Or simply: normalize value if the abstraction itself is unnecessary. Do not turn complex business logic into dense comprehensions. Bad: result = { x.id: transform x for x in items if x.enabled and x.config and x.config.valid } when a loop would make failure cases and rules clearer. Comprehensions are for simple transformations. Bad: return next x for x in values if x.id == id , None is fine when obvious. But do not compress multi-step business logic into nested expressions simply to save lines. Readability first. Be suspicious of: map ... filter ... reduce ... partial ... when a simple loop is clearer. Python often reads better with comprehensions or direct loops. Do not optimize for abstract functional style. Bad: list map lambda x: x.id, projects Prefer: project.id for project in projects Use idiomatic Python. Bad: php def is empty value: str - bool: return len value == 0 Prefer: if not value: when semantics match. Do not wrap obvious built-ins for no reason. Bad: re.sub r"^\s+|\s+$", "", domain Prefer: domain.strip Use regex for regex-shaped problems. Do not use dynamic code execution to solve configuration, expression, or dispatch problems unless the feature explicitly requires it and security implications are understood. Prefer explicit parsing. Do not patch classes/functions at runtime to avoid proper dependency design. Monkey patching may be appropriate in tests or specialized libraries. It should not be normal application architecture. Metaclasses are rarely required in ordinary application code. Be highly suspicious of introducing: class FooMeta type : ... for registration, validation, or convenience. Prefer normal classes/decorators/functions. Likewise, do not introduce custom descriptors for basic validation or computed fields when properties/dataclasses/models solve the problem more clearly. Bad: php class Project: @property def id self - str: return self. id when there is no invariant or encapsulation need. Use plain attributes where appropriate. Python does not need Java-style getters/setters. Bad: project.get id project.set id id for ordinary attributes. Prefer: project.id unless access has real behavior. Do not use: self. project id plus a property purely for encapsulation theater. Python's conventions are enough. Use private-ish fields when they represent internal implementation state. For Mongo/PyMongo: Bad: document: dict str, Any domain = document.get "domain" if isinstance domain, ObjectId : return str domain when the document schema is known. Use a model/TypedDict: class DNSDocument TypedDict : domain: ObjectId then: str document "domain" Or use an ODM model if the project already does. If SQLAlchemy/Django models define: project.id: str project.enabled: bool do not repeatedly runtime-check them inside business logic. Trust the ORM model unless the field genuinely allows null. Bad: repository.find collection="projects", filters={"id": project id}, projection=None, options={} when the application has a clear domain operation. Prefer: project repository.get project id Do not make internal APIs mimic a generic database driver unless generic behavior is genuinely needed. Avoid: Repository → DAO → Store → DatabaseClient → Session for simple persistence. Use the minimum layering that gives useful testability and separation. Do not build elaborate: UnitOfWork TransactionManager TransactionScope TransactionProvider around a small amount of transaction code unless the application genuinely benefits. Use the ORM/database's native transaction API directly where clearer. Bad: class QueueMessage: event: str data: dict str, Any with downstream code doing: if message.event == "domain.map": data = as domain map event message.data Prefer event-specific parsing at the boundary. Example: class DomainMapEvent BaseModel : event: Literal "domain.map" project id: str domain: str Or use discriminated unions if the project's validation library supports them. Parse JSON once. Validate once. Convert into the domain representation once. Do not repeatedly do: json.loads ... dict ... model validate ... asdict ... across layers. Bad: data = json.loads model.model dump json or: payload = json.loads json.dumps data just to convert structures. Use direct conversions or actual typed objects. With Pydantic, do not constantly turn models back into raw dicts: service.run payload.model dump if the service could accept the model/type directly. Dump only at serialization or integration boundaries. Bad: service.create project { "project id": project id, "region": region, } when: service.create project project id, region or a meaningful request object is clearer. Use parameter objects when the values form a real concept or the parameter list is substantial. Do not pass huge context objects where only two fields are needed. Bad: php def sync project payload: ProjectPayload - None: project id = payload.project id rate limit = payload.rate limit if only those fields matter. Prefer: php def sync project project id: str, rate limit: int - None: unless the payload itself is the meaningful domain concept. Bad: php def create project options: dict str, Any - Project: Prefer explicit parameters or a concrete model. Generic option dictionaries push validation problems downstream. Bad: deploy project, force=True, skip cache=False, async mode=True, validate=False, If flags create meaningfully different operations, consider separate functions or a clear options model. Do not create cryptic combinations of booleans. At the same time, do not turn: deploy project id into: DeployCommand options=DeployOptions behavior=DeployBehavior ... without a real reason. Avoid both extremes. Avoid: logger.debug "Entering function" logger.debug "Validating input" logger.debug "Calling repository" logger.debug "Repository returned" logger.debug "Returning result" Log: - failures - important state transitions - external operations - debugging information with real operational value Do not narrate code execution. If the codebase uses structured logging: logger.info "project deployed", extra={"project id": project id}, follow existing conventions. Do not introduce a new logging framework during cleanup. Delete comments like: Check if project exists if project is None: Convert domain to lowercase domain = domain.lower Keep comments for: - business rules - quirks - invariants - external constraints - non-obvious reasoning Comments should explain why. Bad: php def get project project id: str - Project: """Get a project.""" This adds nothing. Keep docstrings for public APIs, complex semantics, parameters with non-obvious contracts, or behavior worth documenting. Do not add huge Google/Numpy-style docstrings to obvious private helpers. Example of unnecessary ceremony: php def normalize domain domain: str - str: """ Normalize the domain name. Args: domain: The domain name. Returns: The normalized domain name. """ Prefer self-explanatory code. Apply anti-slop rules to tests too. Be suspicious of: TestDataBuilder MockFactory FixtureFactory ScenarioBuilder BaseTestCase IntegrationTestHelper for simple tests. Prefer direct setup and pytest fixtures where useful. Do not mock pure internal code unnecessarily. Mock real external boundaries where isolation matters: - network - filesystem - third-party APIs - expensive infrastructure Use real domain objects for internal logic when practical. Good: @pytest.mark.parametrize "value", "expected" , "EXAMPLE.COM", "example.com" , " example.com ", "example.com" , , def test normalize domain value: str, expected: str - None: assert normalize domain value == expected Do not build a testing DSL for three cases. Do not create: BaseServiceTest BaseRepositoryTest BaseIntegrationTest unless there is substantial common lifecycle behavior. Prefer fixtures and composition. Bad: python def safe mock return mock: Mock, default=None : Use the mocking library normally. Do not invent abstraction around standard test tooling without need. Do not remove validation for: - HTTP requests - CLI arguments - queue payloads - config/env vars - webhooks - untrusted JSON - external APIs - schemaless database records - user input - file contents That is where defensive programming belongs. Do not remove: - network timeout handling - database errors - not-found cases - transaction rollback - file-not-found handling - permission errors - subprocess failures - cancellation handling - API-specific exceptions - security checks This is not a request to make code fragile. The distinction is: Defend against external uncertainty, not against correctly typed internal code. This is not a cleanup: value = cast str, data "domain" becoming: value = data.get "domain" if value is None: return "" if not isinstance value, str : return "" if len value == 0: return "" return value if the actual contract says domain is required and is a string. The correct fix is: @dataclass class DomainEvent: domain: str and then: event.domain Whenever you see: safe x as x normalize x extract x convert x resolve x ensure x coerce x trace the value upstream. Ask: 1. Why is this value not already typed? 2. Where does it enter the application? 3. Is that where validation belongs? 4. Can downstream code receive a concrete type? 5. Can the helper disappear entirely? Always prefer fixing the earliest sensible point in the data flow. Search the repository for: Any dict str, Any Mapping str, Any object cast isinstance hasattr getattr setattr vars dict inspect. type try: except Exception except: pass return None return {} return return "" .get or {} or or "" deepcopy Base Abstract Mixin Manager Processor Factory Builder Resolver Converter Mapper Validator Helper Utils Protocol ABC Generic TypeVar create task gather to thread run in executor retry lru cache Do not automatically remove every match. Use them as signals to inspect for unnecessary complexity. Especially review directories/features containing combinations like: base.py interfaces.py protocols.py factory.py builder.py mapper.py converter.py validator.py resolver.py manager.py processor.py helpers.py utils.py service.py repository.py for one small feature. Determine whether each layer has real behavior. Collapse meaningless ones. Before adding or keeping code, ask: Is this defending against something that can genuinely happen here? If no, remove it. Ask: Is this complexity caused by an overly broad type? If yes, fix the type. Ask: Does this helper represent a real concept? If no, inline it. Ask: Does this abstraction reduce total complexity? If no, delete it. Ask: Is this dynamic Python because the problem is actually dynamic, or because the code does not know its own types? If the latter, fix the model. Ask: Would plain Python be easier to understand? If yes, use plain Python. Prefer: php async def handle domain map event: DomainMapEvent, - None: domain = event.domain.strip .lower await domain service.map project id=event.project id, domain=domain, over: php async def handle domain map raw data: Any, - None: data = ensure dict raw data project id = safe string data.get "project id" domain = normalize value data.get "domain" if not project id or not domain: return payload = DomainMappingPayload.from dict { "project id": project id, "domain": domain, } await domain manager.process mapping payload Before editing Python code: 1. Identify the trusted and untrusted boundaries. 2. Determine the actual data shapes. 3. Check whether Any /generic dictionaries are necessary. 4. Understand whether None is genuinely valid. 5. Inspect whether abstractions have real callers/implementations. 6. Prefer fixing data models upstream instead of adding local guards. 7. Do not add new helpers before understanding why the existing type is broad. Before finishing any Python change, inspect the diff. For every added: - helper - class - protocol - base class - mixin - factory - builder - wrapper - fallback - isinstance - getattr - Any - dict str, Any - try/except - nullable type - decorator ask: 1. Does this handle a state that can genuinely occur? 2. Am I compensating for bad typing upstream? 3. Could this code be direct instead? 4. Did I add another abstraction layer? 5. Does this helper have a real reusable concept? 6. Am I hiding invalid input behind an empty value? 7. Am I swallowing an error? 8. Did I introduce dynamic behavior where the shape is known? 9. Did the change increase total complexity? 10. Could any newly added code simply be deleted? If yes, simplify before completing the task. The codebase should trend toward: - fewer Any values - fewer generic dictionaries - fewer runtime type checks - fewer .get chains - fewer silent fallbacks - fewer broad except Exception - fewer tiny helper functions - fewer mapper/converter classes - fewer base classes and mixins - fewer factories/builders - fewer unnecessary protocols - fewer pass-through layers - more precise models - validation concentrated at boundaries - direct attribute access - explicit business logic - idiomatic Python - easy-to-follow control flow The important metric is not line count alone. The important metric is: Can another engineer understand the behavior without navigating defensive machinery and unnecessary abstractions? Do not transform: validated typed input → direct business logic into: Any → isinstance → getattr → safe helper → converter → fallback → wrapper → manager → actual operation The desired flow is: untrusted input → parse/validate once → precise Python type → straightforward business logic The overriding principle is: Make uncertainty explicit at the boundary. Keep trusted Python code simple, typed, direct, and boring.