Blog › ICP guides
Eiffel developer on retainer: Design by Contract precondition violations, postconditions, class invariants, and Eiffel on monthly retainer
October 3, 2026 · ~14 min read
An Eiffel developer was maintaining a manufacturing quality control system built in EiffelStudio. The system recorded sensor measurements through a class method called AddMeasurement. The method carried a Design by Contract precondition: require value > 0.0. Every physical measurement the sensors produced was a positive real number — part counts, torque readings, dimensional measurements — so the precondition was correct, intentional, and had never fired in years of production use.
The developer added temperature compensation logic. In extremely cold factory environments, a calibration adjustment was needed: if ambient_temp < 0.0 then adjusted_value := value * COLD_FACTOR end. The constant COLD_FACTOR had been defined as a negative constant to model a physical edge case in sub-zero ambient conditions where the sensor's response curve inverts. After the compensation, adjusted_value could be negative. The developer then called AddMeasurement with adjusted_value as the argument.
In a debug build with assertion monitoring enabled, the precondition fired immediately: a PRECONDITION_VIOLATION exception. Three measurements taken during a cold-morning calibration run were reported as exceptions rather than being stored in the quality control record. The symptom was visible in the exception log — three entries reading {PRECONDITION_VIOLATION}: value > 0.0 with the feature name AddMeasurement — but the exception named the violated contract, not the temperature compensation code that produced the violating value. The fix was a guard in the caller: adjusted_value := adjusted_value.max(EPSILON) added before the AddMeasurement call, clamping the compensated value to a small positive minimum. Precondition violations: 3 → 0.
The key structural property of this bug is that the violation fires at the contract boundary, not at the code that produced the violating value. The PRECONDITION_VIOLATION exception names AddMeasurement and its require clause. It does not name the temperature compensation branch. In a large codebase where AddMeasurement has many callers, the developer must read the violated clause, understand what value it prohibits, and then trace every call site to find the one that can produce that prohibited value. In a production build compiled with {OPTIMIZE}, assertions may be stripped entirely, meaning the precondition never fires — the wrong measurement is stored silently, with no exception and no log entry, until a downstream quality report surfaces an anomalous reading.
Eiffel and Design by Contract: the language that made assertions first-class
Eiffel was designed by Bertrand Meyer at ISE (Interactive Software Engineering) in 1985 and first released publicly in 1986. Meyer's central contribution was Design by Contract (DbC): the idea that software components specify their obligations and guarantees as part of the class and method syntax, not as separate documentation or informal comments. A method's contract consists of three parts: a require clause (precondition), an ensure clause (postcondition), and an invariant clause at the class level.
The require clause lists what the caller must guarantee before the method executes. ensure lists what the method guarantees to the caller after it returns. invariant is a class-level assertion that must hold after every exported feature call: if a feature modifies attributes in a way that temporarily violates the invariant, the invariant must be restored before the feature returns. Assertions are written directly in the class body, adjacent to the feature they constrain. There is no separate specification language, no annotation framework, and no external contract file — the contract lives in the same source file as the implementation, and EiffelStudio enforces it at runtime.
When an assertion is violated at runtime, Eiffel raises an {ASSERTION_VIOLATION} exception. The specific subtypes are {PRECONDITION_VIOLATION} (a require clause was false on entry), {POSTCONDITION_VIOLATION} (an ensure clause was false on exit), and {INVARIANT_VIOLATION} (the class invariant was false after a feature returned). The exception carries the violated clause text, the feature name, and a stack trace. In EiffelStudio's debug mode with full assertion monitoring, every require, ensure, check, loop invariant, and invariant is evaluated at the appropriate point in execution.
Exception handling in Eiffel uses a rescue block inside the feature definition. A rescue block can inspect the exception object, attempt to repair the violated condition, and then issue a retry instruction to re-execute the feature body from the beginning. The retry mechanism is designed for recoverable failures: if the rescue block fixes the precondition violation and retries the feature, the feature runs again with assertion monitoring active. If the rescue block cannot fix the problem, it should re-raise the exception or set the feature's result to a failure indicator. The {ASSERTION_VIOLATION} class hierarchy allows type-specific rescue: a rescue block can catch {PRECONDITION_VIOLATION} and handle it differently from {POSTCONDITION_VIOLATION}.
In production builds, assertion monitoring can be controlled with compilation pragmas. The {OPTIMIZE} pragma instructs the EiffelStudio compiler to strip assertion evaluations from the generated code, trading runtime safety for performance. This is the most significant operational difference between development and production Eiffel deployments: a precondition violation that fires as a PRECONDITION_VIOLATION exception in a debug build will silently pass through in a production build compiled with {OPTIMIZE}. The method executes with an argument that violates its contract, and the behavior is undefined from the contract perspective — the method was designed to assume the precondition holds, so any computation it performs on a non-compliant argument may produce wrong results without raising any further exception.
Class structure, generics, and void safety in Eiffel
Eiffel class syntax uses explicit structural keywords that make the object model visible. A class definition begins with class CLASS_NAME (or deferred class for abstract classes). Inheritance uses inherit PARENT_CLASS, with optional adaptation clauses: redefine feature_name to override a feature in the subclass; rename feature_name as new_name to change the feature's name in the inheriting class; undefine feature_name to make an inherited feature deferred in the subclass. Multiple inheritance is fully supported: a class can inherit from multiple parents with rename and redefine clauses applied independently to each parent's features.
Feature visibility is controlled with export clauses: feature {NONE} declares private features accessible only within the class itself; feature {ANY} declares public features accessible to all clients; feature {SOME_CLASS} restricts access to a named class and its descendants. This gives Eiffel fine-grained access control beyond the simple public/private/protected model of most object-oriented languages. Retainer work frequently involves feature {NONE} utility routines that perform the actual computation but are wrapped by feature {ANY} methods that carry the require and ensure clauses. When a precondition violation fires, the developer must trace whether the violation is in the public wrapper or in a feature {NONE} implementation routine called internally.
Eiffel generics use a bracketed syntax: LINKED_LIST[G] defines a linked list parameterized over a generic type G; ARRAY[G] defines a generic array. Constrained generics add a conformance requirement: class SORTED_LIST[G -> COMPARABLE] requires that G conform to the COMPARABLE class, meaning instances of G must have a defined ordering relation. Generic classes participate in the contract system: a require clause in a generic class can reference features of G if G is constrained appropriately. Manufacturing QC systems often use generic measurement containers like MEASUREMENT_BUFFER[G -> NUMERIC] where the buffer's AddMeasurement precondition constrains the range of acceptable values for any conforming numeric type.
Void safety is Eiffel's approach to eliminating null pointer dereferences at compile time. The type system distinguishes between attached reference types (A or attached A), which are guaranteed never to be void at runtime, and detached reference types (detached A), which may be void. When void-safe compilation is enabled in EiffelStudio, the compiler enforces that every attached reference is initialized before use and that detached references are checked for voidness before dereferencing. Legacy Eiffel code written before void safety was introduced in EiffelStudio 6.x (circa 2008) uses a pre-void-safety type system where all references are effectively detached and Void dereferences produce runtime exceptions rather than compile-time errors. Migrating legacy manufacturing systems to void-safe compilation is a significant retainer engagement: every feature must be audited for reference initialization order, detached references must be explicitly marked, and creation procedures must assign attached values before the object's invariant is checked.
SCOOP (Simple Concurrent Object-Oriented Programming) is Eiffel's concurrency model, introduced in EiffelStudio 14.05. Under SCOOP, concurrency is expressed through the type system: a separate declaration on a class or reference indicates that the object lives on a separate processor (thread). Feature calls on separate objects are automatically synchronized; the SCOOP runtime manages locks and scheduling without explicit mutex or monitor code. Preconditions on separate features behave differently from non-SCOOP preconditions: a precondition on a separate feature acts as a wait condition rather than a contract violation. If the precondition is not satisfied when the feature call is scheduled, the SCOOP runtime waits until it becomes satisfied, rather than raising a PRECONDITION_VIOLATION. This is a critical difference for manufacturing QC systems with concurrent sensor threads: a precondition that fires as an exception in a single-threaded build may become a blocking wait condition in a SCOOP-enabled build, masking the same contract violation as a performance issue rather than an error.
Typical Eiffel retainer work and what it looks like in a work log
Precondition violation diagnosis is the largest category of Eiffel retainer work that produces no visible artifact independent of the diagnosis session. A require clause like value > 0.0 is semantically load-bearing: the method's implementation may rely on the positive value for logarithm computation, for array index derivation, or for a physical formula that produces imaginary numbers for negative inputs. Relaxing the contract requires understanding the full implementation and all downstream uses. The retainer work is not changing the contract — it is finding the caller that passes a violating value and adding a guard there. Work log entry: “AddMeasurement: require value > 0.0 violated; temperature compensation branch (if ambient_temp < 0.0 then adjusted_value := value * COLD_FACTOR end) produced negative adjusted_value because COLD_FACTOR is a negative constant; fix: adjusted_value := adjusted_value.max(EPSILON) guard added before AddMeasurement call; precondition violations: 3 → 0; 4h.”
Postcondition verification is the second category. An ensure clause may reference old expressions — the value of an attribute at feature entry — to specify a change: ensure count = old count + 1 says the count must increase by exactly one. A postcondition violation fires when the implementation fails to deliver its promise. The diagnostic challenge is that postcondition failures often indicate a logic error deeper in the feature body rather than a simple guard omission. A measurement accumulator with ensure measurement_count = old measurement_count + 1 that fires when a measurement is stored during a concurrent SCOOP operation (where two processors simultaneously call the feature) points to a missing separate declaration rather than a wrong computation. Work log entry: “StoreMeasurement: ensure measurement_count = old measurement_count + 1 violated during concurrent sensor data flush; two SCOOP processor threads both incremented count but one update was lost; StoreMeasurement feature not marked separate; fix: added separate to storage buffer reference; postcondition violations: intermittent → 0; 6h.”
Class invariant violation tracing is the third category, and the most disorienting to diagnose. The invariant is checked on entry and exit to every exported feature. A violation fires as an INVARIANT_VIOLATION exception at the exit of the feature that broke the invariant, not at the feature that originally set up the inconsistent state. In a class with an invariant like invariant upper_limit > lower_limit, a feature that adjusts lower_limit without correspondingly adjusting upper_limit may leave the invariant violated. The exception fires at the exit of that feature. But if the invariant has held for many previous calls, the developer must determine whether the feature itself is wrong or whether an earlier feature set upper_limit to a value that makes the invariant fragile for certain input ranges. Work log entry: “SetLowerThreshold: invariant upper_limit > lower_limit violated on exit; new calibration range set lower_limit equal to upper_limit for zero-tolerance measurement mode; invariant was too strict for zero-tolerance case; fix: relaxed invariant to upper_limit >= lower_limit; added separate require lower_limit <= upper_limit to SetLowerThreshold; violations: 1 → 0; 3h.”
Inherited contract maintenance via require else and ensure then is the fourth category, rooted in the Liskov substitution principle. In Eiffel's contract inheritance model, a subclass may not strengthen a precondition: doing so would allow the subclass to reject calls that the superclass would accept, breaking substitutability. To enforce this, Eiffel's contract inheritance syntax for preconditions is require else new_condition, which logically ORs the new condition with the inherited precondition — the precondition is weakened, never strengthened. Postconditions use ensure then additional_condition, which logically ANDs the additional condition with the inherited postcondition — strengthened guarantees are allowed. A developer who writes require new_condition (without else) in a redefined feature in EiffelStudio will get a compiler warning or error, but a developer who writes a require else with a condition that happens to be more restrictive than the parent's precondition for the current domain may produce a violation that is hard to trace because the inherited-OR logic makes the effective precondition non-obvious when reading only the subclass source.
Track Eiffel developer retainer hours without the status emails
When a 4-hour session diagnoses a Design by Contract precondition violation in AddMeasurement — tracing the temperature compensation branch that produced a negative adjusted_value, identifying COLD_FACTOR as a negative constant, adding the adjusted_value.max(EPSILON) guard, and verifying the fix across all ambient temperature ranges — the work log must name the feature, the violated clause, the argument value, and the fix. HourTab gives your Eiffel retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log that names the precondition and the guard. No client login. No status emails. CSV in, URL out.
How HourTab tracks Eiffel developer retainer hours
Eiffel retainer work is invisible by the same mechanism that makes Design by Contract so powerful: the contract boundary is explicit, but the path from the contract violation back to the originating code is not. A PRECONDITION_VIOLATION exception names AddMeasurement and the clause value > 0.0. It does not name the temperature compensation feature, the COLD_FACTOR constant, or the if ambient_temp < 0.0 branch. A manufacturing system where AddMeasurement is called from dozens of features across multiple sensor classes may require an hour of call-chain tracing before the developer identifies which caller introduced the violating value. That hour produces no deliverable — no new feature, no visible code change, no test added — until the guard line is written.
The work log must name the mechanism: which feature, which require clause text, what value was passed and why, which caller code path produced it, what the guard fix was, and what the before/after violation count was. A log entry that says “fixed precondition bug, 4h” is not auditable. A log entry that says “AddMeasurement: require value > 0.0; temperature compensation branch multiplied by negative COLD_FACTOR; adjusted_value was −0.34 at ambient_temp = −8°C; fix: adjusted_value := adjusted_value.max(EPSILON) before call; violations: 3 → 0; 4h” is auditable, billable, and self-documenting for the next developer who reads the change log.
HourTab gives Eiffel developers a public retainer-hours URL they send to clients — manufacturing quality control teams, defense and aerospace contractors, and financial systems teams that chose Eiffel for its correctness guarantees and Design by Contract methodology. For Eiffel retainers, each work log entry should name the contract mechanism: which feature, which clause type (require, ensure, or invariant), which value triggered the violation, and whether the fix was a guard in the caller, a relaxed invariant, or a contract inheritance correction via require else. Comparative context: Eiffel retainer work has close technical overlap with other environments where formal correctness and contract-like guarantees are central to the maintenance workload — Ada (both are used in safety-critical systems; Ada 2012 introduced contract-checking aspects including Pre and Post that parallel Eiffel's require and ensure, and both languages are maintained by teams where correctness properties carry regulatory weight); Smalltalk (Eiffel's object model was strongly influenced by Smalltalk's message-passing OOP design, and both languages use image-based development environments with live class hierarchies that require similar retainer patterns for legacy system maintenance); and ALGOL (Eiffel's structured ancestry runs through Simula, which was derived from ALGOL 60, and the block-structured procedural discipline of ALGOL is visible in Eiffel's feature and class block syntax). Eiffel is uniquely positioned as the only widely-deployed language where Design by Contract is part of the language specification rather than a library, a framework, or an annotation convention.
FAQ: Eiffel developer retainers
What does an Eiffel developer on retainer typically do?
An Eiffel developer on monthly retainer covers Design by Contract precondition violation diagnosis (identifying which caller passes an argument that violates a require clause; tracing the call chain from the violated feature back to the new code path that produced the non-compliant value; fixing by adding a guard in the caller rather than weakening the contract in the callee); postcondition verification (tracing ensure clauses and old expressions to confirm the method's promised state is actually delivered after execution); class invariant violation tracing (finding which feature modifies attributes in a way that breaks an invariant clause; invariants are checked on entry and exit to every exported feature); inherited contract maintenance via require else and ensure then (ensuring subclasses do not accidentally strengthen preconditions in violation of the Liskov substitution principle); and void safety migration (attached vs detached reference types, void-safe compilation flag, updating legacy code that predates EiffelStudio's void-safe mode).
What Eiffel work is most commonly underlogged?
Precondition violation diagnosis is the most systematically underlogged Eiffel retainer work. A require clause like require value > 0.0 is correct and intentional: the method's implementation may assume a positive value for logarithm computation, for array indexing, or for a physical formula. When a new feature adds a code path that can produce a negative adjusted value — for example, multiplying by a negative COLD_FACTOR constant to model a physical edge case — and then passes that value to a method with a positive-value precondition, the violation fires at the contract boundary as a PRECONDITION_VIOLATION exception. The exception names the contract, not the originating code path. In a production build compiled with {OPTIMIZE}, assertions may be stripped entirely, meaning the precondition never fires — the wrong value is processed silently with no exception and no log entry. Diagnosis requires restoring assertions in a debug build and tracing the full call chain. 3 to 6 hours invisible per occurrence.
What are typical Eiffel developer retainer rates?
Entry-level Eiffel developers with 1 to 2 years covering basic EiffelStudio class maintenance, require/ensure clause reading, and precondition violation diagnosis typically bill at $65 to $120 per hour. Mid-level Eiffel programmers with 2 to 4 years covering Design by Contract debugging, generics, void safety migration, and inherited contract maintenance via require else and ensure then typically bill at $100 to $180 per hour. Senior Eiffel developers with 4 or more years covering SCOOP concurrency, complex class hierarchy invariant tracing, large manufacturing or safety-critical system maintenance, and EiffelStudio optimization pragma tuning typically bill at $145 to $265 per hour. Monthly retainer ranges: $1,200 to $2,800 per month for advisory engagements covering precondition violation diagnosis and contract review (10 to 20 hours per month); $2,500 to $6,500 per month for active manufacturing QC system maintenance and feature development.
What should an Eiffel developer retainer agreement include?
A retainer agreement should specify: EiffelStudio version and license scope (ISE Eiffel is the canonical implementation, available as commercial and GPL editions; the version determines void-safe compilation support and available SCOOP concurrency features); assertion monitoring scope (whether require, ensure, check, loop invariant, and class invariant clauses are monitored at runtime in debug builds, and whether {OPTIMIZE} pragma strips them in production — this affects whether precondition violations surface as exceptions or silent wrong values); void safety scope (whether the codebase uses attached vs detached reference syntax and the void-safe compilation flag; legacy Eiffel code predating void safety requires migration work that is distinct from feature maintenance); contract inheritance scope (whether subclass contract maintenance — require else for precondition weakening, ensure then for postcondition strengthening — is in scope); and hour logging format (the feature name, the violated clause text, the argument value that triggered the violation, the caller code path, and the guard fix applied).
How should Eiffel developer retainer hours be logged?
Log each Eiffel retainer session with: the feature name where the precondition violation occurred (e.g., AddMeasurement); the violated require clause text (e.g., require value > 0.0); the argument value that triggered the violation (e.g., adjusted_value was negative because COLD_FACTOR is a negative constant multiplied when ambient_temp < 0.0); the call site where the violating value was passed (e.g., temperature compensation branch introduced in the latest feature); the symptom (e.g., 3 measurements reported as PRECONDITION_VIOLATION exceptions rather than being stored in the quality control record); and the fix applied (e.g., added adjusted_value := adjusted_value.max(EPSILON) guard before the AddMeasurement call; precondition violations: 3 → 0). For postcondition violations: the ensure clause text, the actual vs expected state, and whether the violation was caused by a side effect or concurrent access. For invariant violations: the invariant clause, the feature that broke it, and whether the fix was in the feature body or the invariant definition.