Lessons from Open Source Software

Photo by Marija Zaric on Unsplash

Lessons from Open Source Software

This whole post is a response to From Contributor to Maintainer: Lessons from Open Source Software by @PatDeveloper. We already had a short chat on Twitter but I think that it's worth its own post.

A short disclaimer: I have absolute respect for Patrick, we worked together on some spatie packages and also he's definitely a great developer and person. He also did an amazing job in automating some of the maintenance work at spatie and other packages.

Creating Open Source Projects

Avoid creating projects that you cannot or will not maintain.

Open-source projects require ongoing attention to address issues, update dependencies, and incorporate new features. If you do not have the capacity or commitment to maintain a project, it is better not to release it to the public.

This is the point I disagree with the most or even in total and was also the starting point for the Twitter thread. I get where Patrick is coming from and understand that point - but I think it should be rephrased a little. It's absolute true that a OSS project/package will require work, time and effort to keep it great and up to date but also grow the userbase. So in case you really want to make the next Symfony, Laravel, TailwindCSS, Livewire, AlpineJS or whatever - better have some spare time you can and want to dedicate to this new project. But these libraries aren't the rule - most libraries will live undetected, slowly growing from 1 to 2 to 5 to 10 to x users over several months or even years.

So it's more a decision you should make: Do you want to create an actively maintained library and put in the effort to market it and everything? Or do you just want to publish some code you already wrote anyway and allow others to get inspired or use it?

To me both are absolutely valid options and it's totally up to you. The best thing is that you can even switch your mind and go to full maintainance mode or reduce to keep it available.

Your code should be easily understood and be maintainable by others.

Clear documentation, consistent coding standards, and well-organized codebases are essential when introducing an open source project. This not only facilitates collaboration but also ensures that others can step in to help with maintenance or contribute new features.

This also slightly goes hand in hand with the first one. Yes, if you want others to work on it and also expect them to provide quality PRs you should deliver first and set the expectation. But as said if you just want to publish code you already have to make it reusable for yourself or others the code can be as shity or good as it is. You wrote it, you have it working and solving at least one specific problem - so you can also publish it as is.

The most common OSS licenses MIT and GPL provide the licensed software AS IS and without any liability or waranty by the maintainer/author. And that's fine, the code is open and every potential user can read through it and decide if they want to use it or not or if they just copy some code or create a fork of it.

Avoid creating packages that are too specific.

The more generalized and flexible your project, the broader its appeal and utility to the community. By focusing on creating versatile and widely applicable projects, you can attract a larger base of users and contributors.

This is a very controversial statement in the maintainer community. There is the group of the "everything has to be as flexible as possible" and the other "it should solve one specific problem in a very specific way". I belong more to the specific team, let me reason why.

Let's say you have a package for an easy to use and drop-in cart and checkout solution. As you use stripe you picked stripe and also use as much stripe UI as possible to make it probably fully working without any backend but only JS and some JSON "DB" file for the products. You release it and it probably even picks up quickly as it's the new and cool 💩. Now you have thousands of users and as stripe isn't supported everywhere around the world and probably also not the preferred payment method in other countries you get a lot of requests to add PayPal support and also other payment providers. We assume that all these providers have the same features so that you "only" would have to implement a manager-driver pattern like thing and all would be happy. Others will ask you to switch to an API and the next one wants a real DB, probably sqlite or all in with MySQL or postgreSQL. The next one wants full control over the frontend and don't use the stripe UI.

But is it what you want? You likely never use these other ones but you will have to maintain all of them for the next years.

So I think that it's better that you solve a problem in an opinionated and specific way that you feel comfortable to maintain for years and can also dogfood yourself than creating generic code you dislike and also can't test/use all on your own. As even if one of these devs contributes the PayPal implementation they will likely not jump on board and be the maintainer for the next years and keep up to date with API changes and everything.

Automate everything.

To streamline the maintenance and management of your projects, use of GitHub workflows and features like Dependabot whenever you can. Automating processes such as merging Dependabot PRs, running test suites, marking issues as stale, and updating the changelog can significantly reduce the manual workload. These tools ensure that your project remains up-to-date and stable without constant manual intervention.

Like said at the beginning that's what I relate the most with patrick and he did a great job! There's nothing really to say about it. Automating your common processes will make it easier for everyone to stick to patterns, guidelines and so on.

Contributing to Open Source Projects

Target projects that you use frequently.

Familiarity with the functionality and nuances of it puts you in a position to provide meaningful features or fixes. Whether squashing bugs that you’ve discovered or implementing features you have use cases for, your contributions will likely address needs that others have also encountered.

Absolutely! There's no real benefit in picking a random repo from GitHub only to contribute something to something. In case you can't find something to solve in your direct dependencies you can also check out the sub-dependencies composer show can help you here.

Your contributions are public and are a reflection on you as an engineer.

Remember that potential employers may look at your previous work. It's important to think carefully about the quality and thoroughness of your contributions; each pull request you make is a demonstration of your abilities and your understanding of software development. By consistently contributing well-structured, tested, and documented code, you not only support the projects you care about but also build a strong professional reputation that can open doors to future opportunities.

In my experience most employers don't take the time or even don't have the knowledge how to check GitHub. In most companies potential candidates are filtered by HR and they are likely not dev focused but will also handle designers and "normal" office people. You will have to get to the developer department (CTO, Head or Lead) who know GitHub and will also not have to check hundreds of candidates but only 10.

And a little "ad": I created opendor.me to solve exactly that and open doors for you by aggregating your GitHub profile and all your contributions in one profile. That profile is also understandable by HR as it starts with a descriptive paragraph of the most important facts about your OSS history.

Open thoughtful pull requests.

The clarity and thoroughness of your PR descriptions make a significant difference in whether or not it gets merged quickly, slowly, or at all. Start by summarizing what the pull request does in a few sentences, then drill down into the important points below. This approach not only helps maintainers understand the scope and purpose of your changes quickly, but also facilitates a timely, smooth review process.

I can fully agree with this. When I get a PR and open the notification the first thing I see is the title, this already sets my expectations and mood. Next thing I see is the PR description. Is there a description at all? Is it well formatted? Does it contain the original problem the contributor had? Does it contain a quick usage example of the new feature?

When I don't have a real idea of the code changes by reading only the title and description it depends highly on my current time. Very likely I will just close the MR again and switch to the next one. As it takes a massive amount of time to read through the PR diff and check with current documentation and tests to get an idea of the intended changes. The problem is that I have to get an idea of the original problem that should be solved, I will generate a quick idea in my head what's likely "the best"/my way to solve it, I will check the real implementation and compare it to my idea and also have to fully check the code for typos, bugs and possible improvements. So talking technically this requires a lot of RAM as I have to do it all at the same time. While with a good description I can do it one after another and have to check the diff only for bugs and improvements as regular code review.

Keep your pull requests small and focused.

Each PR should add a single feature or fix a single bug. This streamlines the review process for maintainers, enabling them to review and merge your code more quickly. Likewise, it’s also easier to address any requested changes promptly. A small, well-defined PR is less likely to introduce unintended side effects and is generally easier to test and validate.

Yes please. Sometimes I get relatively simple PRs but they also change code-style, add doc-tags, automatic changes by their IDE and whatever else. This makes the review process a lot more complex. As things are mixing up and I have to check if it's now a change required for that feature or is it only a change because it got changed. It's totally fine that you have all these things in the pipeline to change. But you should make them separate PRs. That way the maintainer can decide if to accept at all or not and they can reject a code-style change but still accept your new feature.

Include unit tests.

Responsible maintainers will rarely merge code without appropriate tests, as they help ensure that new changes do not break existing functionality. By including unit tests with your Pull Request, you’re respecting the maintainers' time and reduce the back-and-forth that can occur when tests are missing. Even if your PR seems trivial or is already covered by existing tests, demonstrating that you've thought about testing can expedite the merge process and reflect positively on your contributions.

I'm pretty sure that it's only a typo by Patrick but still want to make it clear. It doesn't really matter if you add unit or feature or integration tests. Add tests - that's the point. A test will ensure that your code changes will remain functional during the next years and also privide the maintainer a quick and definitely working usage example.

Update documentation when you make API changes.

If the project you’re contributing to has existing documentation, remember that someone took the time to write it; respect this effort on their part and make sure you update it accordingly, including using a similar tone and style. This ensures that other developers can understand and effectively use the new or modified features you have implemented. Neglecting this step can lead to confusion, undermining the usability and reliability of the project.

That's done very rarely but so important. Primary smaller changes very rarely update the documentation which means that both will drift and the next one has to update more in the docs than necessary. It's also a great way to get your OSS acreer started at all. Primary as a beginner or new user of a package the documentation is so important for you and you likely spot problems a lot faster than the maintainer who doesn't even open the docs anymore to know how things are done. Or probably something changed in a third party and because of that a command doesn't work anymore. Unimportant how small the change is, probably even only a typo - but you can improve the docs and get your contributions started.

Respect the time and effort of maintainers and contributors.

You’re a user of the project and have benefited from the time and effort of others. Acknowledge the hard work and dedication when you can. Demonstrating appreciation and respect allows you to receive it in return. Always be considerate and courteous.

That's sometimes what some people don't understand and treat the maintainer like 💩. And even if you try to ignore it - it takes energy and time. As a user you get something for free - the maintainers don't owe you anything. If someone gives you a gift you can accept or reject it and you can also pimp it a little. But you don't start a rant what a *** person the other one is.

It's totally okay that you dislike my codestyle, my decisions made or whatever and we can have a professional discussion about it. But that will take your time as well and some real arguments.

A different part of it is that you can and should do a self review of your PR. Open the diff and check everything for files that shouldn't be there, some old debug statements/comments or simple typos. This will ensure that you provide what you really wanted and also safes some feedback loops as the maintainer will likely find these things and request changes.

Respect existing code and writing styles.

Remember that this isn’t your project - it’s someone’s, though, and their preferences matter. Whether it’s two spaces for indentation instead of four (or even - gasp - tabs), or the writing style of the documentation, show the original author the respect of using their style preferences.

I already had some examples in "Keep your pull requests small and focused.". Probable the library has some kind of linter and other tools - you should run them to ensure that your code follows the guidelines. The documentation also has an existing structure - so you shouldn't just add to the bottom of the README.md but check where your new section fits best.

Maintaining Open Source Projects

It’s a privilege, not a right.

Being a maintainer and having the ability to triage issues, review and merge Pull Requests and release new versions is a privilege, not a right. This role comes with significant responsibility and trust, and should be approached seriously. Your actions as a maintainer can directly impact the quality and reliability of a project; they will also affect the experience of the project’s users. Likewise, your actions can impact the reputation of others, such as the original project author. Be mindful of this.

Only thing to say:

With great power comes great responsibility.
— Quote Investigator

Get input from others.

When uncertain about how to address an issue or handle a pull request, don’t hesitate to seek input from other maintainers. Collaboration and communication are key to making well-informed decisions that benefit the project and its community. Leveraging the collective knowledge and experience of others helps ensure that the best possible solutions are used.

This really helps. Not only for your own packages but also for others you want to contribute to. Getting early feedback and input means that you don't have to work on something only to see it rejected or with hundreds of change requests. In general it's a good idea to ask first before submitting your code changes.

Be appreciative.

A simple thank you to a contributor or author of a bug report can go a long way. Recognizing the efforts of others not only strengthens open source community but also motivates continued contributions and engagement in the project.

This goes hand in hand with "Respect the time and effort of maintainers and contributors." as it's the same vice versa. Everyone should respect each other independant if they are the author, maintainer, contributor or user.