Blog › ICP guides
Cobra developer on retainer: nil-safety, design-by-contract, require preconditions, nullable types, and Cobra language on monthly retainer
October 1, 2026 · ~15 min read
A Cobra developer was building a configuration processing pipeline. A method was declared with a require precondition block asserting that the config parameter was not nil: require config is not nil. The parameter type was declared as Config — Cobra’s non-nil reference type. The developer assumed that because the parameter type was Config (non-nil), Cobra’s type system would catch any attempt to pass nil at the call site as a compile-time error, and that the require block was redundant defensive documentation. Several weeks after deployment, a RequirementFailure appeared in the production logs: the require config is not nil condition had failed. The developer was puzzled: if the type system prevents nil from being passed as Config, how did nil reach the method at all? The answer was that one call site was invoking the method through a code path that originated at a .NET interop boundary, where the .NET method returned a reference that Cobra’s type system had annotated as Config (non-nil) rather than Config? (nullable), but the actual .NET object could in practice return null in a specific error case. The Cobra compiler accepted the code because it trusted the annotation; the annotation was wrong. The correct fix was twofold: update the interop boundary annotation from Config to Config?, update the method parameter from Config to Config?, remove the require config is not nil precondition, and add explicit nil handling inside the method body. Runtime RequirementFailure: 1 → 0.
The work log said “fixed nil crash in configuration pipeline, 5h.” It cannot explain that the crash originated not from a nil pointer dereference in the traditional sense but from Cobra’s contract enforcement system detecting a violated precondition, that the root cause was a wrong nil-ability annotation at a .NET interop boundary, or that the fix required coordinated changes to three layers: the interop wrapper, the method signature, and the contract block. A client reading that entry sees five hours for what reads like a null check fix. What is invisible is the diagnostic cost: confirming that the call site was correctly passing a non-nil value from Cobra’s perspective, tracing why that non-nil value was actually null at runtime, finding the interop wrapper where the annotation mismatch originated, understanding Cobra’s nil-safety model well enough to know that a Config annotation at an interop boundary is a promise the developer makes to the compiler about the .NET method’s behavior, and redesigning the contract to match what the method actually needs rather than what the developer wished it needed.
Cobra language overview: Python-like syntax with nil-safety and design by contract
Cobra was created by Chuck Esterbrook and first released around 2006. Its design goal is to combine Python-like syntax ergonomics with two features that Python lacks: a nil-safe type system and first-class design-by-contract support. Cobra compiles to C# source code and then to .NET IL via the C# compiler, running on both the .NET Framework and Mono. The generated C# is human-readable, which is useful for debugging: if a Cobra behavior is unclear, a developer can inspect the generated C# to see exactly what contract checks and nil guards were inserted by the compiler.
Cobra’s syntax derives clearly from Python: indentation-based block structure, class Name declarations, def methodName(params) method declarations, for item in collection loops, if condition / else conditionals, and pass for empty blocks. The keyword nil takes the role of Python’s None. Method parameters and local variables are statically typed: def process(config as Config) declares config as type Config. Type inference is available for local variables with the var keyword: var result = compute() infers the type from the return type of compute. The use keyword imports namespaces: use System.Collections.Generic. Classes are declared with properties using the get / set syntax or with pro (property with automatic backing field): pro name as String.
Cobra found adoption among developers who wanted Python-level readability with stronger guarantees than Python’s dynamic type system provides. The .NET compilation target means that Cobra programs can call any C# or VB.NET library, consume NuGet packages, and produce assemblies usable from other .NET languages. The testing system is particularly distinctive: inline test blocks in a class body compile into the class as methods and are run by the Cobra test runner without a separate test framework. A class can define its own tests immediately adjacent to the code being tested, which reduces the friction of maintaining test coverage as code changes.
Cobra’s nil-safe type system: non-nil by default and the ? suffix
Cobra’s most significant departure from Python and from most object-oriented languages is its nil-safety model: all reference types are non-nil by default. A variable declared as config as Config cannot hold nil. The compiler enforces this at compile time in Cobra’s static type checking mode: assigning nil to a Config variable or passing nil where a Config parameter is expected produces a compile-time error. To declare a nullable reference type, the type is suffixed with ?: config as Config? can hold nil or a Config instance.
This inversion of the usual .NET default — where all reference types can be null by default, and non-nullability is opt-in via nullable reference type annotations added in C# 8 — means that Cobra codebases written correctly do not produce NullReferenceException from unexpected nil dereferences. Every nil that exists in a Cobra program is either a ?-typed variable that the developer explicitly declared as potentially nil, or a value that crossed a .NET interop boundary without a correct annotation. The second category is the dominant source of nil-related errors in Cobra retainer work: .NET methods that can return null are wrapped in Cobra code, and if the wrapper declares the return type as Config rather than Config?, the compiler accepts the code but the runtime nil eventually surfaces either as a NullReferenceException or, if a require block catches it first, as a RequirementFailure.
The nil-safe access operator . followed by a property name returns nil if the receiver is nil, rather than throwing: user?.address?.city returns nil if user is nil or if user.address is nil, and returns the city string if both are non-nil. The result type of a nil-safe access chain is always nullable: user?.address?.city has type String?. This is the idiomatic Cobra pattern for navigating nested structures that may contain nil at any level, and it is the primary tool for handling Config? parameters inside a method body: config?.primaryEndpoint?.url produces a nullable URL rather than crashing. The conditional cast operator x to? Foo? attempts to cast x to Foo? and returns nil if the cast fails, equivalent to C#’s x as Foo pattern. Unlike a direct cast which throws InvalidCastException on failure, to? is safe to use at type boundaries where the concrete type is uncertain.
Cobra’s design-by-contract system: require, ensure, and invariant
Cobra implements Bertrand Meyer’s design-by-contract methodology as a first-class language feature. The three contract block types are: require (preconditions), ensure (postconditions), and invariant (class invariants). A method with a require block:
def process(config as Config, retryCount as int)
require
config is not nil
retryCount >= 0 and retryCount <= 10
body
# method implementation
ensure
result is not nil
The require block contains Boolean expressions that are evaluated when the method is called. If any expression is false, Cobra raises RequirementFailure with the source text of the failed condition — so a failure message might read RequirementFailure: config is not nil or RequirementFailure: retryCount >= 0 and retryCount <= 10. This is significantly more informative than a NullReferenceException or an ArgumentException with a hand-written message: the error message is derived directly from the source code of the condition that was violated, which immediately identifies the contract and the call that broke it.
The ensure block contains postconditions checked when the method returns normally. The special variable result inside an ensure block refers to the method’s return value. ensure result is not nil verifies that the method never returns nil; ensure result.count >= old count verifies that some metric increased (using the old keyword to refer to the value of an expression at method entry). If an ensure condition is false, Cobra raises EnsureFailure. Postconditions are powerful for methods that accumulate or transform state: the postcondition documents the transformation guarantee, and the runtime check verifies it on every call during testing.
The invariant block is written at the class level and contains conditions that must be true after every public method call on an instance of the class. Class invariants are checked automatically by Cobra’s runtime after every public method returns. An invariant that says _count >= 0 verifies that the count field of a collection class never goes negative regardless of which public method was called. Designing invariants requires careful thought about what the class guarantees to all callers; over-specifying invariants creates maintenance burden as the implementation evolves, while under-specifying them leaves the class’s correctness guarantee undocumented and unenforced.
Cobra’s inline testing system and the test block
A Cobra test block is written directly inside a class body, adjacent to the methods it tests:
class RetainerTracker
def hoursRemaining(total as int, used as int) as int
require
total >= 0
used >= 0
used <= total
body
return total - used
test
t = RetainerTracker()
assert t.hoursRemaining(20, 8) == 12
assert t.hoursRemaining(10, 10) == 0
expect RequirementFailure, t.hoursRemaining(10, 15)
The test block is compiled into the class as a method named test_hoursRemaining (or similar, depending on the Cobra version) and run by the Cobra test runner with the cobra -test flag. The expect ExceptionType, expression syntax within a test block asserts that evaluating the expression raises the specified exception type. In the example above, expect RequirementFailure, t.hoursRemaining(10, 15) verifies that calling the method with used > total correctly raises RequirementFailure from the used <= total precondition. This makes contract testing first-class: the developer can verify not just that correct inputs produce correct outputs but that incorrect inputs are caught by the contracts, which is the primary value proposition of Cobra’s design-by-contract system.
The inline test block model eliminates the friction of maintaining a parallel test file structure: a developer adding a new method to a class adds the test block for that method immediately below the method definition. This proximity means tests are more likely to be updated when the method changes. In retainer work, the test block is also the specification: when a contract is relaxed (for example, when a parameter type changes from Config to Config?), the test block should be updated to remove the expect RequirementFailure case for nil input and add a new case verifying that nil input is handled correctly inside the method. The retainer session log entry “updated config pipeline nil handling, 5h” should name the expect RequirementFailure test case that was removed, the new nil-handling test case that was added, and what the before and after RequirementFailure count is.
Typical Cobra retainer work and what it looks like in a work log
A Cobra retainer typically covers three recurring categories of work. The first is nil-safety system work: auditing the .NET interop boundary annotations to ensure that all .NET method return types that can return null are declared as Foo? rather than Foo; migrating parameters and local variables from non-nil to nullable when the code evolution makes the original non-nil assumption incorrect; and redesigning nil-safe access chains as the model deepens. This work reduces the probability of unexpected RequirementFailure or NullReferenceException in production and moves nil-handling responsibility from runtime contracts to the type system. The work log entry “updated interop boundary annotations for new API, found 3 nil-ability mismatches, 4h” is auditable: the annotation changes are in the diff. Without naming the specific methods whose annotations were wrong and explaining what the correct annotation should be, the entry is not self-explaining.
The second category is contract system work: authoring new require / ensure / invariant blocks for methods that lack formal contracts, redesigning existing contracts that are too strong or too weak, and updating contracts when method signatures evolve. Contract design is a form of specification writing and requires the developer to articulate what the method actually guarantees, not just what the developer hopes it does. A five-hour session spent designing postconditions for a complex data transformation method produces no visible feature, no bug fix, and no performance improvement — it produces specification code that will catch future bugs by making the method’s guarantee executable. The work log entry needs to say what guarantee was specified and why it was not already captured in the type system alone.
The third category is .NET interop maintenance. Cobra codebases that interface with C# libraries encounter the same interop challenges as other .NET-hosted languages, with the additional complexity of Cobra’s nil-safety model. A C# library upgrade that changes a method from returning a non-null object to potentially returning null requires a Cobra annotation update: the wrapper declaration changes from Config to Config?, and all downstream code that uses the result must be updated to handle the nullable variant. The work log entry reads “updated interop wrapper for library v3 upgrade, changed Config return to Config?, updated 4 downstream usages, 3h” and the hours are justified by the scope of the change.
Track Cobra developer retainer hours without the status emails
When a five-hour session traces a RequirementFailure to a wrong nil annotation at an interop boundary, migrates a parameter from Config to Config?, and updates the contract and test blocks to match, the work log needs to say that — not just “fixed nil crash.” HourTab gives your Cobra retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log that names the annotation that was wrong, the contract that was relaxed, and the RequirementFailure count before and after. No client login. No status emails. CSV in, URL out.
How HourTab tracks Cobra developer retainer hours
Cobra retainer work is invisible by the same mechanism that makes it valuable: the primary output of a successful session is the elimination of a class of runtime error, not the addition of a new feature. A client who sees “5h — updated nil handling in config pipeline” cannot assess whether five hours was proportionate to changing a type annotation from Config to Config? and removing a require block. The work log needs to say: the interop boundary was annotating a .NET method’s return type as Config (non-nil); the .NET method can actually return null in the error case; the annotation was wrong; the fix was to change the annotation to Config?; the parameter type of the downstream method was changed from Config to Config?; the require config is not nil precondition was removed because it is now invalid for a nullable parameter; nil handling was added inside the method body; the test block was updated to replace the expect RequirementFailure case for nil input with a case verifying the new nil behavior; RequirementFailure count: 1 → 0. That log entry is auditable and justifies the hours.
HourTab gives Cobra developers a public retainer-hours URL they send to clients — typically .NET platform teams using Cobra for its nil-safety and contract guarantees, research groups that adopted Cobra for its design-by-contract model, and organizations maintaining Cobra codebases that interface with C# libraries and need ongoing nil-annotation maintenance as the C# libraries evolve. For Cobra retainers, each work log entry should name the mechanism at the level of the nil-safety analysis: which annotation was wrong, what the correct annotation is, how many downstream usages were updated, and what the before and after RequirementFailure and NullReferenceException count is. Comparative context for scope discussions: Cobra retainer work has conceptual overlap with retainer work on other contract-driven languages and nil-safe systems, including Eiffel (where design-by-contract originated), Kotlin (non-null by default with ? suffix for nullable), and Swift (optional types with ? and !). The retainer rate reflects the specific combination of design-by-contract expertise and .NET platform knowledge: senior Cobra developers bill at $130 to $240 per hour because that combination is not well-served by general .NET developers or by developers who know contracts theory without .NET production experience.
FAQ: Cobra developer retainers
What does a Cobra developer on retainer typically do?
A Cobra developer on monthly retainer covers nil-safety type system work (all reference types non-nil by default; Foo? nullable variant; require preconditions vs nil-safety annotations; .prop? nil-safe access; x to? Foo? conditional cast); design-by-contract system (require preconditions, ensure postconditions, invariant class invariants; RequirementFailure / EnsureFailure with source-text condition; old keyword for precondition-to-postcondition value capture); inline testing (test blocks, expect ExceptionType, expr syntax, cobra -test runner); .NET interop annotation (wrapping nullable .NET methods as Foo? return types); and contract hierarchy design (which assertions belong in require vs type annotations vs runtime guards).
What Cobra work is most commonly underlogged in a retainer?
RequirementFailure diagnosis and precondition restructuring (tracing a RequirementFailure to a wrong interop annotation; migrating the parameter from Foo to Foo?; updating the require block and test cases; 4 to 8 hrs invisible); non-nil to nullable migration (auditing all parameters that should be Foo?; updating nil-safe access chains; 5 to 10 hrs invisible); nil-safe operator chain design (replacing nested nil checks with .prop? chains; evaluating to? conditional casts at type boundaries; 4 to 7 hrs invisible); contract hierarchy design (determining which assertions belong in require vs ensure vs invariant; designing strong postconditions for data transformations; 4 to 7 hrs invisible); and .NET interop nil management (auditing boundary annotation correctness as C# libraries evolve; 3 to 6 hrs invisible).
What are typical Cobra developer retainer rates?
Entry-level Cobra developers with 1 to 2 years of experience covering Cobra syntax, basic nil-safety, require and ensure blocks, and .NET interop basics typically bill at $55 to $100 per hour. Mid-level Cobra programmers with 2 to 4 years covering RequirementFailure diagnosis, nullable type migration, nil-safe operator design, contract hierarchy, and inline test authoring typically bill at $85 to $165 per hour. Senior Cobra language developers with 4 or more years covering advanced contract design, class invariants, nil-safety architecture across large codebases, and .NET interop boundary nil-annotation typically bill at $130 to $240 per hour. Monthly retainer ranges: $1,600 to $3,200 per month for advisory engagements (15 to 25 hours per month); $3,500 to $10,000 per month for full engagement Cobra development.
What should a Cobra developer retainer agreement include?
A Cobra developer retainer agreement should specify: nil-safety scope (parameters in scope for nullable migration; acceptable RequirementFailure rate; interop boundary audit); contract system scope (require / ensure / invariant authoring; contract relaxation when parameters become nullable; test block updates for contract changes); .NET interop scope (which .NET methods need Foo? annotation; boundary audit cadence); test system scope (inline test blocks vs external test framework; expect contract violation tests); and hour logging format (require: condition text, why it failed, type changed from Foo to Foo?, nil handling added, RequirementFailure count before and after; nil-safe: operator chain before and after, nil dereference risk eliminated; contract: block type, condition, violations caught, fix applied; interop: method, annotation corrected, usages updated).
How should Cobra developer retainer hours be logged?
Log each Cobra retainer session with: RequirementFailure category (method; require condition that failed; why: caller passed nil from interop boundary returning null; parameter type before: Config; parameter type after: Config?; require block updated or removed; nil handling added inside method; test block updated; RequirementFailure before: 1; after: 0); nil-safety migration category (class; parameters migrated from Foo to Foo?; return types migrated; dereferences updated to .prop?; potential nil dereferences before: N; after: 0); contract design category (block type; condition text; violations caught during authoring; final condition; test coverage added); interop boundary category (.NET method; annotation changed from Foo to Foo?; downstream usages updated; before: potential RequirementFailure; after: nil handled explicitly); and for all categories, the before and after RequirementFailure and NullReferenceException rates, since the ratio is the primary quality metric for Cobra retainer work.