Coding for Quality

Cobra has language-level support for high quality coding via doc strings, unit tests, contracts, nil tracking and assert statements.

Doc Strings

A doc string provides documentation for the declaration it is written with. Developers are more likely to write documentation in the first place if it can be put right with what it describes. They are also more likely to maintain it. Here is an example:

class Utils shared def countChars(s as String, c as char) as int """ Returns the number of instances of c in s. """ count = 0 for ch in s if c == ch count += 1 return count

Unit Tests

A unit test validates that a class, property or method is working properly. It can also be viewed as a form of documentation because it provides examples of using what it validates. Here is the above example with a unit test added:

class Utils shared def countChars(s as String, c as char) as int """ Returns the number of instances of c in s. """ test assert Utils.countChars('', c'x') == 0 assert Utils.countChars('x', c'x') == 1 assert Utils.countChars('X', c'x') == 0 # case sensitive assert Utils.countChars(' ! ! ', c'!') == 2 body count = 0 for ch in s if c == ch count += 1 return count

As with doc strings, unit tests are more likely to be written and maintained when they can be coupled with their respective declaration.

Contracts

A contract describes what a method requires in order to be called and what it ensures when it returns to its caller. Each item in the contract is expressed as a boolean expression in the language so that it can be executed at runtime. Contracts help catch errors earlier when they are easier to diagnose. They also document a method in a rigorous fashion.

class Customer var _contacts as List<of Contact> get contacts from var def addContact(contact as Contact) require contact not in .contacts contact.name contact.customer is nil ensure contact.customer == this .contacts.count == old .contacts.count + 1 body contact.customer = this _contacts.add(contact)

The above features can substantially improve the quality of a project when used from the beginning. However, they are all optional features and can be skipped when you are working on not-so-serious programs. The nil tracking feature is not optional, however.

Compile-time Nil Tracking

Cobra calls it nil as do Smalltalk, Objective-C and LISP. Other languages call it NULL, Nothing, None and null. It's customary in most languages to be able to pass nil anywhere, or at least where instances of classes are expected. But in Cobra, nil can only be passed when allowed by a nilable type: one suffixed by a question mark (?). So, for example, the following code will generate a compile-time error because Utils.countChars was declared to take a String, not a String?:

Utils.countChars(nil, c'x')

The following code does not fail at compile-time or run-time because the if statement proves that the call is safe:

class Foo def bar(s as String?) if s # same as "if s is not nil" print Utils.countChars(s, c'x')

The upshot of nil tracking is that runtime exceptions about nil reference errors are almost entirely eliminated. That's not the case in C#, VB or Python which all throw exceptions for foo.bar when foo is nil. Furthermore, the Cobra compiler can report multiple errors at a times vs. only one at a time for languages that throw an exception at run-time. And like the other quality features described before, this one also serves as a form of documentation: at a glance you can tell which parameters permit nil and which do not.

Nilability applies to all types including return types, local variables and class variables.

Assertions

The assert statement provides an easy way to verify the internal state of your code. If the verification fails, an exception is raised.

... assert i > 0 ... assert name ... assert obj.foo < bar

The exception reports the source code of the condition, the file name, the line number, the current object and optional information provided as a second argument to the assert. However, it's rarely necessary to provide a second, informational argument because a failed asserion also reports the value of every subexpression of the condition. For the last example above, a failure will report the value of obj.foo, obj and bar. This nifty feature makes assertions quicker to write and easier to diagnose.

You might note that the same functionality can be provided by a library call such as Util.assert(cond, info), but that would have two drawbacks:

  1. The info argument is always evaluated even when the assertion passes (and most of them do).
  2. The breakdown of the condition is not given unless you spell it out (and maintain it) in the info argument.

Hence, assertions deserve to be a language level feature. And like Cobra's other quality-related features, the convenience is intended to encourage frequent usage in every day coding.

Where to next?

The How To programs are small Cobra programs the demonstrate specific tasks such as how to:

Your next stop should be How To.

See Also