Code Quality
These principles guide how we write and review code in Kolibri. They apply across the full stack — Python, JavaScript, Vue, and infrastructure — and reflect patterns that have proven valuable in practice.
For language-specific conventions, see Frontend code conventions and the coding conventions section in the project’s AGENTS.md.
Every concern lives at exactly one layer
If a behavior like validation, retry, error handling, or permission checking is implemented at one layer, do not reimplement it at another. Choose the layer that owns the concern and trust it. Redundant implementations create conflicting behavior and obscure which layer is actually in control.
In Kolibri, this means:
Permission checks belong in
KolibriAuthPermissionsandRoleBasedPermissionson models — not duplicated in frontend route guards and API viewsValidation logic belongs in serializers or model
clean()methods — not scattered across both the viewset and the serializerDo not introduce global or shared mutable state to coordinate between modules. If two components need the same data, pass it explicitly or put it behind a single owner (e.g., a composable with module-level refs)
Prefer the simplest implementation that works
Do not introduce abstraction layers until a concrete second use case demands it. Flat is better than nested. An if/else is better than a strategy pattern when there are two cases. Extract complexity only when it reduces total code.
Every layer, class, and indirection must justify its existence. Do not design for speculative future requirements — if a need has not materialized, the code to support it should not exist. When uncertain, make code easy to replace rather than easy to extend.
Preserve existing comments
Only remove a comment if it describes code that has been deleted or is provably incorrect. When modifying code that has comments, update the comments to reflect the changes. Do not strip comments to “clean up” a file.
Interfaces stay small
Every public method needs justification. If something can be private, it must be. Push implementation details behind the interface boundary.
In Kolibri:
Keep the
valuestuple inValuesViewsetminimal — only fetch fields that are actually neededComposables should return a focused public API — not expose every internal ref
A growing public surface area is a design smell; refactor to reduce it before continuing
Tests assert behavior, not implementation
Test inputs and outputs. Mock only at hard boundaries: network, filesystem, external services. Do not mock internal modules or classes to isolate units — test them through the real call chain. If refactoring working code breaks a test, the test was wrong, not the code.
In Kolibri:
Frontend tests use Vue Testing Library which encourages testing from the user’s perspective — query by text, role, and label rather than component internals
Backend tests call API endpoints through Django’s test client and assert on response data, not on internal method calls
See Testing for TDD principles and Backend Testing for backend patterns
Identical code is only duplication if it changes for the same reason
Do not extract shared functions or base classes from code that merely looks similar. Two pieces of code that happen to have the same implementation but represent different domain concepts must remain separate. Premature unification creates false coupling — when one concept’s rules change, the shared abstraction becomes a liability.
Only deduplicate when the knowledge is genuinely the same. Three occurrences of a pattern (the “Rule of Three”) is a reasonable threshold before extracting a shared abstraction.
Do not store what can be computed
Do not add fields to data structures that are derivable from other fields in the same structure. Expose derived values as methods or computed properties.
In Kolibri:
Use
computed()in Vue composables for derived state rather than maintaining separate refs that must be kept in syncUse
annotate_querysetinValuesViewsetfor computed database fields rather than post-processing in PythonIf caching is needed for performance, keep the cached value private — never expose redundant state in the public interface
Let errors propagate
Do not wrap every call in try/catch blocks that just log and rethrow, or that catch broad exception types only to obscure the original failure. Let exceptions propagate to the layer that can actually handle them meaningfully.
If something that “cannot happen” happens, crash — a dead program does less damage than a crippled one continuing to run on corrupted state. Only catch an exception if you have a specific recovery action for that specific failure mode.
In Kolibri, Django REST Framework’s exception handling will catch unhandled exceptions and return appropriate error responses — there is no need to wrap every view method in a try/except.
Tell, don’t ask
Do not reach into an object, inspect its internal state, make a decision based on that state, and then update the object. That pattern scatters the object’s logic across its callers and makes the real rules invisible. Instead, tell the object what you want done and let it manage its own state.
In Kolibri:
Use
RoleBasedPermissionsdeclaratively on models rather than checking roles manually in viewsComposables should expose actions (
fetchChannels()) rather than forcing callers to manipulate internal refs directlyAvoid method chains that traverse multiple levels of abstraction to reach into nested structures
Whoever allocates a resource is responsible for releasing it
Do not open a file, connection, transaction, or handle in one function and close it in a different function. The function that acquires the resource should release it, using the language’s appropriate scoping mechanism.
In Python, use context managers (with statements) for files, database connections, and transactions. In JavaScript, use onUnmounted in composables to clean up event listeners, timers, and polling intervals that were set up in onMounted.
Prefer composition over inheritance
Do not use class inheritance to share behavior between types. Inheritance couples the child to the parent’s implementation — when the parent changes, the child breaks silently.
In Kolibri:
Vue composables are preferred over mixins for sharing component logic — composables use explicit composition rather than implicit merging
Use interfaces or protocols to define shared behavior contracts, delegation to reuse implementation
Reserve inheritance only for true “is-a” relationships where substitutability is required and the hierarchy is shallow (e.g.,
FacilityextendingCollection)
Externalize values that change independently of code
Do not hardcode credentials, feature flags, environment-specific URLs, port numbers, logging levels, or business-rule thresholds as constants or literals in source code. Extract them to configuration that is loaded at startup.
In Kolibri, runtime configuration is managed through kolibri.utils.conf.OPTIONS and Django settings modules (kolibri/deployment/default/settings/). Plugin options use options.py files. Wrap configuration access behind a consistent API so code does not depend on how or where configuration is stored.
Follow existing naming conventions and project vocabulary
Match the naming style of the language (snake_case in Python, camelCase in JavaScript) and the conventions already established in the codebase. Use the same terms the project already uses for domain concepts — do not introduce synonyms.
In Kolibri:
A group of learners is a
Collection, not a “group” or “class” (Classroomis a specific kind ofCollection)Content items are
ContentNodeobjects, not “resources” or “materials”Use
Facility,FacilityUser,Classroom,LearnerGroup— these are the established model names
Names should reveal intent: avoid generic names like data, result, info, item, or temp when a more specific name exists.
Do not weaken existing tests
Do not modify or delete existing tests unless the behavior they test has been intentionally changed. If new code breaks existing tests, fix the code, not the tests. Never loosen assertions, add workarounds, or reduce coverage to make a failing test pass.
Existing tests encode the team’s understanding of correct behavior. Weakening them to accommodate new code masks regressions and erodes the test suite’s value over time. If a test seems wrong, verify with the team before changing it.
See Testing for TDD principles and general testing best practices.
Do not rely on undocumented behavior
If a behavior is not specified in the API contract, language spec, or library documentation, do not depend on it — even if it works today. This includes assumptions about dictionary ordering, implicit type coercion, timing of concurrent operations, default values that are not documented, and the internal structure of third-party objects.
If you must rely on a specific behavior, assert it explicitly so failures are immediate and obvious rather than silent and delayed.