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 a block, make that block its own function.
- Keep comments short. One line is best, two lines at most. If a comment needs more than two lines, make the code clearer, or move the explanation to a doc.
- Keep files between 100 and 300 lines. A file that continues to grow does too many things.
- Avoid abbreviations. Spell names out. Short names are correct only for loop
variables and for obvious abbreviations (
doc,e). - Reuse. Write as little code as possible. If you are about to write something that sounds general, search for it first. It can exist already.
- Build the minimum that works, then iterate. Do not add structure for future requirements that no one asked for.
- Fail loud at the boundary. If an external call fails, raise the error. Do not catch it and fall back. The operator retries with the button.
- A new utility that is specific to one doctype lives in that doctype's module.
- A utility that more than one doctype uses lives in its own module. Import it directly, not through a God-module that re-exports it.
- Module-level helper functions are better than static methods for logic that
does not need
self. - If two methods always run back-to-back, make them one method.
- Always write tests. Make sure that they pass before you call the work done.
- Use
tearDownwithfrappe.db.rollback(), so that the tests run again without a wipe of the database. - Mock only what you must. Use real objects and database records where you can.
- Test names must be long enough that a failed test tells you what broke, without a read of the body.
- When you test that the code blocked or rejected an operation, assert the specific error message or the final state. Do not assert only that an exception occurred.
- Use
frappe.db.set_valuefor a single-field update on an existing record. Usedoc.save()when several fields change together. - Use the controller class directly:
Site("Site", name)instead offrappe.get_doc("Site", name). This is important when the controller is in the same file. It is shorter, and it gives the type checker and the reader the concrete type. - Use
frappe.get_cached_docfor documents that the code reads often and does not change. - Use
frappe.db.existsfor an existence check, instead offrappe.get_valuewith a null check. ignore_permissions=Trueis acceptable in scheduled jobs and background hooks that run as Administrator, where the permission model does not apply.