I’ve run ical.net, an RFC-5545 (icalendar) library for .NET since ~May 2016. It’s basically the only game in town if you need to do anything with icalendar-formatted data. (Those .ics files you get as email attachments are icalendar data.)
A lot of these fall into the “pretty obvious” category of observations.
1) Release notes matter
If nothing else, it serves as a historical reference for your own benefit. It also helps your users understand whether it’s worth upgrading. And when your coworkers ask if a version jump is important weeks after you’ve published it, you can point them to the release notes for that version, and they’ll never ask you again.
2) Automation is important
One of the best things I did when I first figured out how to make a nuget package was push as much into my nuspec file as I could. Everything I learned about various do’s and don’ts was pushed into the code in the moment I learned it.
Not everything in ical.net is automated, and I think I’m OK with that for now. For example, a merge doesn’t trigger a new nuget package version. I think that’s probably a feature rather than a bug.
I suspect I’ll reach a second tipping point where
3) Document in public
Keep your emails to 3-4 sentences, Hanselman says. Anything longer should be on a blog or wiki or on your product’s documentation, FAQ or knowledge base. “Anywhere in the world except email because email is where your keystrokes go to die,” he says.
That means I reply to a lot of emails with a variation of “Please ask this on StackOverflow so I can answer it in public.” And many of those answers are tailored to the question, and then I include a link to a wiki page that answers a more general form of the question. Public redundancy is okay.
Accrete your public documentation.
4) Broken unit tests should be fixed or (possibly) deleted
When I took over dday.ical, there were about 70 (out of maybe 250) unit tests that were failing. There was so much noise that it was impossible to know anything about the state of the code. My primary aim was to improve performance for some production issues that we were having, but I couldn’t safely do that without resolving the crazy number of broken unit tests.
The first thing I did was evaluate each and every broken test, and decide what to do. Having a real, safe baseline was imperative, because you never want to introduce a regression that could have been caught.
The corollary to this is that sometimes your unit tests assert the wrong things. So a bugfix in one place may expose an bad assertion in a unit test elsewhere. That happened quite a lot, especially early on.
5) Making code smaller is always the right thing to do
(So long as your unit tests are passing.)
Pinning down what “smaller” means is difficult. Lines of code may be a rough proxy, but I think I mean smaller in the sense of “high semantic density” + “low cognitive load”.
- Reducing cognitive load can be achieved by simple things like reducing the number of superfluous types; eliminating unnecessary layers of indirection; having descriptive variable and method names; and having a preference for short, pure methods.
- Semantic density can be increased by moving to a more declarative style of programming. Loops take up a lot of space and aren’t terribly powerful compared to their functional analogs: map, filter, fold, etc. (I personally find that I write more bugs when writing imperative code. YMMV.) You won’t find many loops in ical.net, but you will find a lot of LINQ.
I think a preference for semantic density is a taste that develops over time.
6) Semantic versioning is the bee’s knees
Given a version number MAJOR.MINOR.PATCH, increment the:
- MAJOR version when you make incompatible API changes,
- MINOR version when you add functionality in a backwards-compatible manner, and
- PATCH version when you make backwards-compatible bug fixes.
This seems like common sense advice, but by imposing some modest constraints, it frees you from thinking about certain classes of problems:
- It’s concrete guidance to contributors as to why their pull requests are or are not acceptable, namely: breaking changes are a no-no
- Maintaining a stable API is a good way to inspire confidence in consumers of your library
And by holding my own feet to the fire, and following my own rules, I’m a better developer.
7) People will want bleeding-edge features, but delivering them might not be the highest-impact thing you can do
.NET Core is an exciting development. I would LOVE for ical.net to have a .NET Core version, and I’ve made some strides in that direction. But the .NET Core tooling is still beta, the progress in VS 2017 RC notwithstanding. I spent some time trying to get a version working–and I did–but I couldn’t see any easy way to automate the compilation of a .NET Core nuget package alongside the normal framework versions without hating my life.
So I abandoned it.
When the tooling is out of beta, I expect maintaining a Core version will be easier and Core adoption will be higher, both of which improve the ROI with respect to development effort.
8) It’s all cumulative
Automation, comprehensive unit test coverage with a mandatory-100% pass rate, lower cognitive load, higher semantic density, etc. All these things help you go faster with a high degree of confidence later on.
9) People are bad at asking questions and opening tickets
And if you’re not okay with that, then being a maintainer might not be a good fit for you.
- No, I really can’t make sense of your 17,000-line Google Calendar attachment, sorry.
- No, I won’t debug your application for you, just because it uses ical.net on 2 lines of your 100+ line method, sorry.
- No, I’m not going to drop everything to help you, no matter how many emails you send me in a 10 minute time interval, sorry.
All of these things are common when you run an open source project that has traction. Ask anyone.