Guidelines for writing good code in this project.
- Choose clean code over clever code.
- Write object-oriented code. Behaviour belongs on the object it describes.
- Keep functions small — 10 lines is a good target. If a function needs a comment to explain what a block does, that block should be its own function.
- Keep comments short — one line is best, two lines at most. A comment that needs more is a sign the code should be clearer or the explanation belongs in a doc.
- Keep files between 100 and 300 lines. A file that keeps growing is a sign that it is doing too many things.
- Avoid abbreviations. Spell names out. Short names are fine only for loop variables or genuinely obvious abbreviations (
doc,e). - Reuse. Write as little code as possible. If you are about to write something that sounds general, check whether it already exists.
- Build the minimum that works, then iterate. Do not add structure for hypothetical future requirements.
- Fail loud at the boundary. If an external call fails, raise — don't swallow and fall back. The operator retries by clicking the button.
- A new utility that is specific to one doctype lives in that doctype's module.
- A utility reused across doctypes lives in a dedicated module and is imported explicitly — not through a God-module re-export.
- Module-level helper functions are preferable to static methods for logic that does not need
self. - If two methods always run back-to-back, they should probably be one method.
- Always write tests. Make sure they pass before considering work done.
- Use
tearDownwithfrappe.db.rollback()so tests are re-runnable without wiping the database. - Mock only what you must. Prefer real objects and DB records over mocks.
- Test names should be long enough that a failing test tells you exactly what broke, without reading the body.
- When testing that something was blocked or rejected, assert the specific error message or the resulting state — not just that an exception was raised.
- Use
frappe.db.set_valuefor single-field updates on existing records; usedoc.save()when multiple fields change together. - Prefer the controller class directly —
Site("Site", name)overfrappe.get_doc("Site", name)— especially when that controller is defined in the same file. It's shorter, and it gives the type checker and the reader the concrete type. - Use
frappe.get_cached_docfor documents that are read frequently and not mutated. - Prefer
frappe.db.existsfor existence checks overfrappe.get_valuewith a null check. ignore_permissions=Trueis acceptable inside scheduled jobs and background hooks that run as Administrator, where the permission model does not apply.