Thursday, June 23, 2016

How to document your build process for an open source C# project

As an Open Source contributor...

I find an interesting open source project that I want to contribute to. I fork/clone the repository to my machine. Then I have to figure out how to build it.

Is there a solution file? Or a script?

I try something and the build fails. Do I need a certain SDK or Visual Studio feature installed? Which version?

I get it to build and then I try to run the tests. 1/3rd of them fail, because they are looking for something that isn't installed on my machine.

If I'm lucky (!?) I find a document in the repository that claims to be build instructions, but it is jumbled and clearly out of date. I try to follow it, but something I need to install is no longer available, or not compatible with my version of Windows. Will a newer version of that thing work OK?

Uggh, what a mess.

As an Open Source maintainer...

I put together a cool little project in my spare time and post it online. It's simple and straightforward to build and run tests. 

Then a contributor complains that they can't build it. What information could possibly be missing? It's simple and straightforward, right? I write a small text file explaining the obvious instructions. The contributor tries to follow it but is even more confused. I don't have time for this.

Uggh, what a mess.

A solution

My solution is AppVeyor. I treat AppVeyor as the reference build environment.

Here's how:
  1. https://ci.appveyor.com/
  2. New Project, select your project.
  3. Settings -> Do what you need to get a green build + tests
  4. Settings -> Export YAML. Add it to your repo.
  5. Delete the AppVeyor project
  6. New Project again, but this time configure nothing. It will use the settings from your repo.
  7. Confirm that build + tests are still green

Now the instructions for how to build + run tests are in source control. Anyone can read them. There won't be any missing details. If a dependency changes, I won't miss updating the instructions, because AppVeyor will report my build is broken. 

No more mess.

Saturday, June 11, 2016

An example of good engineering

I often advocate for good engineering practices and the path to Zero Bugs. Talking about these things is great and all, but concrete examples are important. I recently published an open source project that I think is a good example of this kind of work.

I hope you will copy some of the ideas to use in your own projects. You can read the source here: https://github.com/JayBazuzi/ValueTypeAssertions

Great tests

Code Coverage is a dumb measure, especially in this case. There are very few branches in the code; two tests would hit 100% coverage. 

In this project, you can pick any line of code and modify it to be incorrect, and you'll get a test failure that tells you exactly what is wrong. That is much more valuable than any code coverage number.

I can't guarantee that it has 0 bugs, but I can say that every type of bug I have ever imagined or experienced in this code is covered by a test. 

The tests are organized like a spec, using namespaces/folders to organize tests the same way as if you were writing a spec. Each name indicates what aspect of the system's behavior is being covered. 

The tests are super-fast, which makes the edit-build-test cycle a happy experience. 

ReSharper

ReSharper settings are included in the repository. All sources have been formatted with these R# settings. This makes it easy to keep formatting / style consistent. 

If a random person on the internet decides to make a contribution, I don't have to explain the project's style - they can just let ReSharper take care of that. 

ReSharper Code Inspections are 100% clean, further helping keep the code clean and consistent.

AppVeyor

Every Pull Request is automatically validated by AppVeyor, including build + unit tests.

C# Warn-as-error is turned on for the AppVeyor build. I believe it's important to have 0 warnings - either heed the warning if it matters, or disable the warning if it doesn't. But I don't want to slow down my edit-build-test cycle just because if a warning, so I don't set warn-as-error on the desktop. But I dot set it in AppVeyor, to to ensure that all changes have 0 warnings before they hit master. 

AppVeyor runs ReSharper Code Inspections, again ensuring there are 0 issues before merging to master. This is especially important because not everyone has ReSharper.

The AppVeyor web site lets you edit build settings online. It's a convenient way to tune the settings. Once I had them just right, I downloaded the appveyor.yml file and added it to the repository. Then I deleted my AppVeyor project and recreated it from scratch, to ensure that no online edits were required -- everything is in the repo. If anyone wants to fork this project on GitHub and set up the same build, that will be easy.

NuGet

Each AppVeyor build produces a NuGet package, which means we know that there aren't any problems in the .nuspec file or anything like that.

When a commit is merged to master, a special AppVeyor build runs to generate an "official" nuget package which is then automatically uploaded to the nuget.org package repository. (The API key is encrypted). AppVeyor automatically updates the version number, and it includes a "-beta" tag so no one expects it to hold to any Semantic Versioning guarantees. 

When semver becomes important for the projet, I will implement a one-touch release process to nuget with non-beta versoin numbers.

The project itself

The whole purpose of this project is to help you get a step closer to zero bugs. 

It embodies all I have ever learned about how to implement equality in C#. Everything I have read; every mistake I have made; every mistake I can imagine making. It makes it easy to eliminate a class of errors: "C# class with incomplete or incorrect equality implementation".

It reduces the barrier to addressing Primitive Obsession, which means fewer bugs in the rest of your system, too.

The project is small

This is quite a small project. You may think that your codebase, being far larger and more complex, would not be amenable to this kind of engineering. I admit that I haven't proven otherwise. And even if you believe it would be possible and valuable to do it on your big project, you may not see how to map these ideas from here to there. Sorry.

But in some ways, the fact that it is small is part of its success. I have found a single need and satisfied that need in a single package. You can adopt this package without taking on any other requirements - no opinionated framework here. It adheres to the Single Responsibility Principle. It does what is needed and nothing else. Any time you can make a project do that, it's a win.

ValueTypeAssertions

The Problem

Primitive Obsession is one of the most pervasive code smells out there. You can address it by moving a primitive in to a simple class.


I call the resulting class a "value type", but don't confuse it with C#'s notion of a value type, which doesn't get its own heap allocation, and is passed-by-value to other methods, and is a source of bugs if it's mutable. I mean "a type that represent a value in some domain".
If you want to implement equality on that class, there are a lot of tricky details that are easy to get wrong, at least in C#. For example:


This will throw an exception when trying to cast the Bar to a Point. So you try to fix it:


This will throw when trying to call null.GetType(). Uggh.

You probably want to override operator==() as well.


The compiler tells you to implement operator!=() to go with it, so you copy/paste and change the method name:


Oops, you forgot to negate the check. Bug.

If the value in question is a case-insensitive identifier of some sort, it's important that the GetHashCode() is implemented correctly. Don't do this:


Maybe you want to implement IEquatable<>, too, and you better get these details right there, too.

Many programmers don't test these details at all, or they test a few but not all, and they have to repeat the same set of tests each time they introduce a new class. If you discover a new rule (ToString() should follow equality, right?) you have to update all the tests.

Prior Art

Assertion libraries typically have an equality assertion. For example, in NUnit:

    Assert.AreEqual( new Point(7,8), new Point(7,8) );

That is insufficient. It only tells you that one of the equality checks you've written is correct, and doesn't catch all the other cases listed above.

The Solution

ValueTypeAssertions addresses all the mistakes I have ever made, or seen made, or can imagine when implementing equality in C#. Grab it from NuGet, and write a unit test like this:

    ValueTypeAssertions.HasValueEquality(new NtfsPath("foo.txt"), new NtfsPath("foo.txt"));


This says "these two objects should equal, in every way that C# recognizes equality".

  ValueTypeAssertions.HasValueInequality(new NtfsPath("foo.txt"), new NtfsPath("bar.txt"));


Which says the same thing about not being equal.

If some part of your value should be case insensitive, just add another assertion:

  ValueTypeAssertions.HasValueEquality(new NtfsPath("foo.txt"), new NtfsPath("FOO.TXT"));

If you wrap two values, assert the combinations:

  ValueTypeAssertions.HasValueInequality(new Point(1, 2), new Point(1, 8));
  ValueTypeAssertions.HasValueInequality(new Point(1, 2), new Point(0, 2));

You can find the source on GitHub.

Feedback

Do you find this useful?

What change would make it more useful to you?

Is there a name for this that would be more obvious?