Now that AI codes for cents, contributors are paid for the delivery, not the code—so no mercy for sloppy pull requests anymore.
Show full content
Someone submitted an issue to one of our open GitHub repositories a week ago,
suggesting a new feature.
We didn’t respond, since we
didn’t notice it.
Today, someone else submitted an implementation of the feature in a pull request.
I rejected it and explained that the feature request must first be
accepted by the
project architect,
and only then would it be worth
making a pull request.
The author of the PR got offended.
He won’t be back.
Good.
A year ago, we would have been sad to lose an
enthusiastic contributor.
Today, we don’t care.
Cruel Intentions (1999) by Roger Kumble
For years you sold coding skill at $50 an hour.
AI now codes for cents.
But you still want $50.
We are still ready to pay.
Not for the code—the code is free.
For the delivery.
You take what Claude Code wrote and you walk it to our door.
You are the courier, not the coder.
The margin is what we pay for trust:
that what you deliver, we can merge without re-checking.
So the delivery must be flawless.
We pay for the delivery, not the code
A PR without a description is a package with no label—refused.
An unfocused PR is two parcels in one box—refused.
A PR too large to read is an oversized package—refused.
A PR sitting in review for weeks is a delivery that never arrived—refused.
A PR for an unapproved feature is a parcel for an address we never accepted—refused.
What we forgave yesterday, when we still needed the code, we no longer forgive.
The market for couriers is huge.
We can afford to choose.
So the bar rises, not falls.
Working code is now the minimum.
We expect discipline, speed of delivery, clear and detailed communication,
and readiness to re-work.
Above all, we expect you to understand our
rules
and obey them.
AI coding agents will make software disposable, like fast fashion replaced tailored clothing, creating demand for more programmers, not fewer.
Show full content
When I was five years old, I inherited the shoes of my older brother.
Not because our family was poor, but because the shoes were good.
My grandma told me that when she was a fiancée her dowry consisted of a few skirts.
Not because she was poor.
She wasn’t.
Because the skirts were good and rather expensive.
Now, in 2026, to get a new pair of shoes or a new skirt, I just buy them at a mall down the street.
I don’t hesitate to throw away the old ones.
The same will happen with the software, thanks to AI coding agents.
25th Hour (2002) by Spike Lee
A few days ago someone (I lost the link) shared a funny story on LinkedIn.
He showed a message from his former boss.
The boss was asking for a fix to software written twenty years ago.
The author of the story proudly claimed that some software, if written properly, can survive a generation.
The software in the story is similar to the skirt of my grandma.
It was made to survive a few decades.
Because it was expensive to make.
That’s why the boss is not hiring a new programmer to re-write the software.
He asks for a fix.
Just like my grandma would not throw away a skirt if it got a hole.
It would go to a tailor to get a patch.
Many of us wonder what may happen when AI agents, like Claude Code, dominate the market.
Programmers, especially junior ones, may be fired en masse.
Look at what Jack Dorsey just did: terminating contracts of about 40% of his tech staff.
Large companies indeed will fire programmers, but not because the new world doesn’t need human coders.
It’s because the new world doesn’t need large software companies.
We’ll toss our software, not update it
What the world is looking forward to is a devaluation of software craftsmanship.
Just like a skirt is no longer valuable, except for some high-end brands, software won’t be either.
Developing a new ERP system would cost a few thousand dollars and take a few days of work.
Just like it recently took a week to create a new web browser.
Oracle, Adobe, Microsoft, and JetBrains will run out of business.
When someone needs an IDE with new language support, they won’t wait for JetBrains to release it next year, maybe.
They will go to a software shop around the corner, pay a few hundred bucks, and get it next Monday.
When someone needs a new feature in Photoshop, they won’t wait for Adobe.
They will buy a new Photoshop from a friend, with the feature and maybe a few more.
When a company needs their accounting system to support a new logistics optimization scheme, they won’t go to Oracle.
They will re-write the entire Oracle Fusion, for a few thousand dollars.
We won’t wait for new releases of the old software we love.
We’ll toss them away and buy new ones.
The market will need more programmers, not fewer.
In order to sew good skirts for all women, a city may need a few dozen tailors.
Because every skirt costs a lot and is worn for decades.
In order to manufacture throw-away one-time skirts, the same city needs many more people.
Obviously, these new people are not the tailors of the good-old-days quality.
But they make many skirts, thousands per day.
Just like fast fashion replaced tailors with factory workers and machine operators, fast software will replace programmers with AI operators.
And the market will demand many of them.
Many more than large software companies employ today.
Large companies will lose their monopoly on complexity.
A small software shop will be able to build a new IDE, a new Photoshop, or even a new Linux.
For a few thousand dollars.
The importance of open source software will continue to grow.
It will become the primary provider of ingredients for AI and its operators.
The new world will need more programmers (AI operators) than it needs now.
Because the demand for custom software will soon start growing.
Everyone will want their own Photoshop.
Every developer will want their own IDE and their own Linux.
And they will throw them away without hesitation.
Just like I throw away my shoes every year and get new ones.
Single Page Applications, once a solution for slow browsers, are now a performance bottleneck due to multiple HTTP round-trips.
Show full content
It seems to be popular to design websites as Single Page Applications (SPA).
Instead of showing a new HTML page on every click, an SPA sends a lightweight skeleton with JavaScript.
The JS makes HTTP requests, receives JSON, and injects data into the DOM.
On each user action, the page doesn’t reload—only the DOM changes.
Such an architecture, once a response to slow browsers and unreliable networks, is now a bottleneck.
The page is built of fragments, each requiring its own HTTP request.
No matter how fast each request is, the multiplication diminishes all optimization efforts.
A network of web pages, where the user progresses through the application
by selecting links, resulting in the next page being transferred
to the user and rendered for their use.
Simply put, each action leads to an HTML page reload.
AJAX paved the way for the SPA: instead of HTML, the server sends JSON, and the browser reconstructs the page locally.
Then came the frameworks: Angular (2010), React (2013), Vue (2014).
Each formalizes the same idea: the browser hosts the application, the server delivers data.
Imagine a simple online calculator where a user enters “2+3,” clicks “Submit,” and sees the result right next to the input field.
No page reload.
The DOM remains the same.
The JS inside the browser changes only the result of calculation, in a single <div/>.
The primary justification for this architecture is performance.
If servers are slow, rendering a full HTML page takes longer than just a JSON with the result of calculation.
If the network is unreliable, re-delivering the entire HTML takes longer than just a small JSON document.
Also, if browsers are slow, making a tiny change in the DOM works faster than a full page reload.
This is true, for a trivial example.
However, when an SPA gets larger, the frontend has to make dozens of round-trips to the backend.
Look at what Facebook and LinkedIn are doing while rendering your home page.
A rather simple UI with just a list of recent posts gets filled up by multiple pieces, each leading to its own HTTP request,
sometimes taking more than a few seconds to complete rendering a page.
Their UX sucks, if you ask me.
Their architects are stupid?
Nope.
The very idea of SPA is flawed.
The architects of Facebook and LinkedIn are the hostages of it.
They can’t make their websites run faster, because they, by design, are built of fragments retrievable from the backend.
They must make multiple HTTP round-trips.
The performance penalty of SPAs is structural, not accidental.
Even if HTTP/2 multiplexes requests, the UI still waits for JSON to arrive in order—classic
head-of-line blocking at the application layer.
Worse, one request often reveals permissions, feature flags, or entity identifiers needed for the next, turning parallel calls into a waterfall.
Caching doesn’t help either: dozens of endpoints, each with its own TTL, rarely produce a full cache hit,
and partial hits still force the browser to assemble and reconcile the page at runtime
Meanwhile, the browser must juggle layout stabilization, loading indicators, and partial failures—all before meaningful content becomes visible.
What was once a solution for small DOM updates, in an era of slow browsers and unreliable networks, has turned into a dead-end for web design.
State belongs on the server while HTML is the primary delivery artifact, not JSON
Most web architects simply can’t make their websites as fast as Stack Overflow, which is not an SPA.
It delivers the entire HTML page, rendered on the server using Razor, in one <50ms request.
It does use client-side JS components selectively, but these are isolated and don’t negate the central role of server HTML for the initial experience.
Their UX is one of the best on the modern web, if you ask me.
Rendering a full page on the server may still be a slow operation.
It may, and it often will.
However, this problem is solvable, for example, with the help of caching.
The server is in charge of the data and the state of navigation, making caching possible.
Literally every large, content-heavy, consumer-facing SPA I can think of is horrible in terms of UX.
Even Gmail is not an exception.
Their UX would be noticeably better if they followed the principles of Roy Fielding and reloaded the page every time an email is opened.
I’m not kidding.
Instead of writing code comments that decay and mislead, let LLMs generate documentation on-demand and fail the build when code is too obscure for them to explain.
Show full content
Writing code documentation is a pain.
Not writing it leads to even bigger pain—we can’t comprehend the code.
However, writing it and then forgetting to update it causes the ultimate pain: it lies and confuses us.
How about we cure all three pains at once: prohibit all comments!
How do we know what the intent of the code is if we don’t have any comments?
We ask an LLM to explain it to us.
What if the LLM fails to explain and confesses its inability?
Then, we automatically fail the build and blame the author of the code.
Thus, we introduce a new quality gate: Code Interpretability Score.
The build passes only if this score is high enough.
Full Metal Jacket (1987) by Stanley Kubrick
The best minds in software engineering have long dreamed of self-documenting code.
In 1974, Brian Kernighan and Phillip James Plaugersaid that
“the only reliable documentation of a computer program is the code itself.”
In 2004, Steven McConnell in Code Complete claimed that
“the main contributor to code-level documentation isn’t comments, but good programming style.”
In 2008, Robert Martin in Clean Code suggested that
“if our programming languages were expressive enough, or if we had the talent to subtly wield those languages to express our intent,
we would not need comments very much—perhaps not at all.”
They all wanted the same thing: code that explains itself.
They just lacked the tools to enforce it.
Why do we write comments at all?
A Java method of a hundred lines may take hours to understand.
A tiny Javadoc block saves this time:
/**
* Recursively finds the shortest
* path between two nodes in the graph.
*/
int[] shortest(int[][] g, int a, int b) {
// A hundred lines of code go
// here, which we have no desire
// to read and understand.
}
Comments promise to help us but fail in two distinct ways.
First, they are unclear.
David Parnas once said that
“documentation that seems clear and adequate to its authors is often about as clear as mud to the
programmer who must maintain the code six months or six years later.”
What the author considers obvious, the reader finds cryptic.
Second, they decay.
Being static metadata, comments do not evolve automatically with the code.
If the implementation of the shortest() function stops being recursive, we may forget to update the Javadoc block.
Such negligence leads to hallucinating documentation that causes bugs, broken trust, and wasted debugging time.
In 1999, Andrew Hunt and Dave Thomas in The Pragmatic Programmer warned that
“untrustworthy comments are worse than no comments at all.”
A recent analysis of 13 open source projects
demonstrated that out-of-date comments are not rare but common.
Now we have a tool that solves both problems: the LLM.
Instead of writing the Javadoc block manually, we let the IDE generate it on-demand.
The LLM reads the hundred lines of code, comprehends it, and summarizes the intent in a single English sentence.
Modern models accomplish this task better than most humans.
The documentation is always fresh because it is generated from the current code, not from a stale comment written months ago.
But we can go further.
We can integrate an LLM into the build pipeline and ask it to assess the Code Interpretability Score (CIS) of every function.
If the model has low confidence in explaining the logic, this signals that the code is too clever or convoluted.
The compiler can enforce a threshold: if the CIS is too low, the build fails.
This transforms readability from a subjective preference into an objective, measurable quality gate.
Once this gate exists, manual comments become not just unnecessary but harmful.
They introduce a second source of truth that can contradict the code.
The logical conclusion: prohibit them entirely.
This forces developers to write clean, structured logic that is inherently machine-interpretable.
Robert Martin wished for more expressive languages.
He didn’t know about LLMs.
Today, we don’t need better languages—we need an LLM that can interpret any language.
If the LLM can’t explain the code, we blame the programmer and stop the build.
We are thinking about making EO, our experimental object-oriented language, this restrictive.
JavaScript was an elegant prototype-based class-free language until TypeScript and ES6 classes ruined it.
Show full content
In 1995, Brendan Eich was hired by Netscape and asked to create a language for their HTML browser.
Rumors say, he designed Mocha in 10 days, later renamed to LiveScript, and then to JavaScript.
It was planned to make it similar to Scheme, a LISP-syntax language.
Instead, to please the crowd of C++/Java coders, it was made syntactically similar to Java.
In 2008, Brendan made a tragic mistake: he donated $1,000 in support of Californian anti-gay marriage law.
In 2014, he joined Mozilla as a CEO and the crowd remembered his anti-diversity gesture.
He had to step down and founded Brave Software, the developer of the Brave browser.
Somewhere around that time they started to kill JavaScript.
Still doing it pretty good, thanks to recent ECMAScript updates and TypeScript.
Platoon (1986) by Oliver Stone
In the JavaScript created 30 years ago, objects were primitive associative arrays of properties.
Either data, a function, or another object may be attached to a property of an object.
Let’s see what Brendan said about JS in 2008:
I’m happy that I chose Scheme-ish first-class functions and Self-ish prototypes as the main ingredients.
First-class functions mean that it’s possible to use a function as a value assignable to a variable.
And then he concluded (pay attention, it’s C not C++):
Yet many curse it, including me. I still think of it as a quickie love-child of C and Self.
Self, which he refers to a few times, is a prototype-based object-oriented language
designed by David Ungar and Randall Smith in Xerox PARC, then Stanford University, and then Sun Microsystems.
Self doesn’t have classes, unlike Java or C++.
Instead, it only has objects.
To create a new object in Self we make a copy of an existing object, known as prototype, and then modify some of its slots (attributes).
In Self, objects don’t have types: all method calls are dispatched in runtime.
For example, we ask a book to rename itself:
book rename: "Object Thinking".
The rename is the method of the book that we call with a single string argument.
The computer doesn’t know anything about the book until it’s time to call the rename method.
Obviously, such a duck typing has its performance drawback.
Every rename leads to a search in a virtual table of book.
To the contrary, C++, where types are known in compile time, can dispatch rename() instantly:
Book b;
b.rename("Object Thinking");
Types (classes in C++ and interfaces in Java) and type annotations are helpful—to the compiler.
To us humans they are a burden.
They require us to do the work of the compiler.
We have to pollute our code with messages like: “This object b is of type Book, please remember.”
The compiler must be smart enough to understand it without our hints.
This is a debatable topic though.
Some believe that type annotations help programmers better understand the code and make fewer mistakes.
I’m also in favor of fewer mistakes, but would rather expect the compiler to infer types automatically, without my annotations.
If I do b.rename() and b is known to be a car instead of a book, I would expect the compiler to figure this out on its own and refuse to compile.
Anyway, JavaScript was designed as a prototype-based dynamically typed language with a minimalistic syntax that resembles Java.
It worked perfectly fine until the industry decided to “fix” it.
JavaScript worked perfectly fine until the industry decided to fix it
In 2008, Mozilla and others proposed ECMAScript 4, which included classes, modules, and other features.
Microsoft took an extreme position, refusing to accept any part of ES4.
Chris Wilson, Microsoft’s Internet Explorer platform architect, criticized ES4 for trying to introduce too many changes.
Brendan Eich accused Wilson of spreading falsehoods and playing political games.
ES4 was abandoned, and classes were dropped.
Then, in 2012, Microsoft created TypeScript, a JavaScript with type annotations and classes.
Since classes weren’t in the standard, Microsoft made their own.
Finally, in 2015, ECMAScript 6 added classes (among other features) to the JavaScript specification.
Many ES4 features, including classes, were revived in a “maximally minimal” form.
The crowd of Java/C++ developers got what they wanted.
Class-free programming is JavaScript’s contribution to humanity.
Unfortunately, not anymore.
It looks like the developers of recent versions of JS believe in something else.
Or maybe they don’t want to make a contribution to humanity anymore.
Maybe they just want to make the crowd happy.
Type annotations and classes don’t match with the concept of class-free object-based programming of JavaScript.
They came from Java or C++ but don’t fit in.
Some programmers may find them helpful, but only because they are used to seeing them in other languages.
JavaScript is not Java, even though the names look similar.
It’s sad to see how a once straight object-centric language paradigm turned into a diversity of unmatchable and suboptimal features.
P.S. JavaScript is still a great language if you ignore classes and type annotations.
When I write in JavaScript, I don’t use them.
Look at the code in the yegor256/jo repository.
It illustrates the Junior Objects book of mine.
I’m proud of this code.
“People using classes in JavaScript will go to their graves never knowing how miserable they were” - Douglas Crockford https://t.co/D2Hpegn0vY
Sales reps and programmers can either fight external obstacles to earn their pay, or convince you, the founder, to pay for their time regardless of results; make the internal path harder.
Show full content
Let’s say, you are a startup founder, like myself.
Try to hire a sales guy.
Offer him a commission-only payment scheme.
Listen to his reaction: he will demand that you pay a fixed salary too, on top of commission.
Try to convince him that commission-only is a more reasonable and motivating setup.
Goto 1.
After a number of iterations you realize that the mission is impossible.
Sales people are good at selling and the best thing they sell is the idea that their time must be compensated.
Even if they don’t sell the product to your customers.
If you don’t buy the idea, they go find another loser who will.
Something similar happens when you try to pay programmers by result.
They easily convince you to pay for their time.
And you do.
City of God (2002) by Kátia Lund
A sales rep doesn’t know how to write code.
Most of them don’t even know how computers work.
However, he perfectly knows how to bullshit people.
That’s exactly why we need him.
Because we don’t know how to bullshit people and we don’t want to learn it.
Now, two strategies lie in front of him.
He can use the skill against the prospects on the market and turn them into paying customers.
He can also use the same selling skill against you, the owner of the startup.
Instead of selling the product to the market he can sell himself to you.
He can sell the idea that even if he fails to sell the product to the customers he still deserves a decent weekly paycheck.
What do you think, which sale is easier to make?
What would you do in his shoes?
The answer is obvious.
The customers are far away and they have no mercy.
If they don’t like the offer, they simply hang up on him and that’s it.
You, on the other hand, sit next to him in the same office and can’t hang up.
You are the low-hanging fruit.
You are the weakest prey he can reach out to.
In order to make him focus on external obstacles, you should make internal ones harder to overcome
A customer is an external obstacle that he must overcome in order to get paid, as a sales commission.
You are an internal obstacle, which he may also overcome to get a fixed weekly payment.
You need him to fight the external obstacle.
However, he is free to choose the easiest path.
In order to make him focus on external obstacles, you should make internal ones harder to overcome.
Joseph Stalin once said that “in the Soviet army it takes more courage to retreat than advance.”
This war-time concept seems relevant to the sales guys you hire.
It must be harder for them to talk you into paying them a fixed salary than to convince a prospect to buy your product.
Emotionally, for you it may be rather challenging to constantly push him back.
Just like it’s often hard to say “No” to a vagrant begging for a dollar at the corner.
Beggars, unless they are physically disabled, also, just like sales people, have two possible life strategies.
Either find a job or beg at the corner.
The begging strategy, for the vagrant and for the sales rep, is easier to pursue.
Programmers are not much different.
They also have two strategies.
They can solve technical problems by merging qualified pull requests.
They can also persuade you that you must pay for their time, not their pull requests.
Which obstacle is going to be harder for them to overcome depends on you.
First, you set up a formula for measuring their contribution.
Second, you bind their paychecks to it: they get paid not by you,
but by merged pull requests.
Finally, you taboo the very possibility of discussing time-based compensation.
Taboo the very possibility of discussing time-based compensation
What you get is a technical team focused on resolving external problems.
The team will advance because it will be pointless to retreat.
You simply won’t pay them for their time.
No matter how many times they repeat “I was working hard the entire weekend.”
Incentives shape behavior.
If you reward excuses, you buy excuses.
If you reward results, you get results.
As a founder, your job is to eliminate the temptation for your team to sell you
anything other than tangible artifacts.
By breaking your software into small open-source packages, you make the entire product easier to maintain—and significantly higher in quality.
Show full content
I don’t like monolithic repositories.
They keep multiple projects together, often written in different languages, by different teams.
Unfortunately, Google, Facebook, and Yandex favor them.
Primarily, according to them, monorepos reduce integration overhead.
They do, but at the cost of quality.
In smaller repositories we can develop better code.
Морфий (2008) by Алексей Балабанов
When a repository is smaller you can achieve higher quality, for a number of reasons:
You can be stricter on style.
It’s easier to keep a thousand lines consistently formatted than a million.
With a thousand lines, you can configure ESLint to its maximum, enabling as many rules as you can find.
Stricter control over code stylistics leads to cleaner code.
You can write deeper tests.
Integration (or deep) tests are inevitably slow.
In a smaller repository, a good integration test coverage doesn’t mean a slow build.
In a larger repository—it does.
A slow build is something a team tries to avoid, thus jeopardizing the coverage.
You can review more pedantically.
In a larger repository it may be harder to remember all the aspects of design.
A pull request that affects different seemingly unrelated code parts
may be a challenge to review.
Even if you are the architect.
You can write a README.
Maybe you have noticed already: large open source projects have short and sketchy README files.
They can’t make them much longer without them becoming as large as a book.
All they can do is redirect the reader to the documentation website.
The inability to explain the entire scope in a single file leads to scope creep.
Contributors struggle to understand the borders of the project.
This leads, among other bad things, to code duplication.
You can release frequently.
In a larger repository, frequent reintegration may be expensive, in both time and money.
In a small repo, a build of a few seconds is not a dream of programmers, it’s their reality.
Not only CI is cheap, but also CD.
After every small change you can publish a new release, with its own version.
In a monorepo, we tend to wait until a portion of changes accumulate.
You can use AI agents effectively.
It is no secret that modern LLMs have limited context windows.
A million lines of code can’t fit into even the largest of them.
Even ten thousand lines, let alone a million, is more than an LLM can digest.
By keeping a repository small we do a big favor to our little friends: AI agents.
You can on-board faster.
Larger codebases are usually older and more chaotic, full of legacy code.
It takes longer to start making meaningful contribution to such a repository.
Monorepos attract
long-termoffice-based
contributors
who care about job security more than about code quality.
You can expect responsibility.
In larger codebases, the very idea of code ownership is hard to maintain.
Programmers can hardly feel responsible for the code written and modified by others.
Smaller repositories, on the other hand, emotionally attach people to code.
You can go open source.
No matter how much your boss loves
open source, you can’t put your entire enterprise monorepo on GitHub.
However, if you extract a small part of it, you can.
The code that is open, visible and criticized by many people,
is allegedly of a higher quality.
In summary, you should look for an opportunity to extract a piece of code as a standalone package.
Then, insist on making it open source.
Then, promote it in the community.
Then, quit your office job and join Zerocracy.
When your pull request won’t merge, don't fight it—fail fast, split your changes, file bugs, and move on smarter.
Show full content
You’ve made a pull request, but it won’t merge.
A reviewer says it’s not good or the tests don’t pass.
No matter what, you can’t get it into the master branch.
You keep fixing the branch, keep convincing the reviewer, keep hating the tests.
Stop.
Try smarter tactics.
Amores Perros (2000) by Alejandro González Iñárritu
1. Give Up Instantly
First, fail fast.
Give up quickly.
If it doesn’t go through smoothly, close it.
If the reviewers’ complaints are more than stylistic issues, your understanding of the architecture is flawed.
Ask yourself, how did this happen?
Why did you, a smart programmer, get a wrong understanding of the architecture?
Obviously, it’s not your mistake.
It’s a bug in the repository.
Its README isn’t complete, its code isn’t clean enough, its documentation is outdated.
What do you do?
You blame them by submitting bug reports.
Then, when they fix the repository, you try again, with a new pull request.
2. Take a Smaller Bite
Most likely they don’t complain about all the changes you’ve made.
Something looks good to them, while they refuse to accept something else.
Good, remove the bad parts from your pull request.
Don’t waste time trying to sell the entire package in one go.
Instead, give them as much as they’re ready to accept.
In the end, you will merge a few pull requests instead of one.
The more, the better, at least for us in Zerocracy, where we reward each merged pull request.
3. Blame Them Wisely
This may be a defect in their existing codebase.
Pretty often it is.
Your code can’t merge, not because it’s broken, but because one of the existing tests is flaky.
This is tricky and it may get ugly.
Don’t get negative or frustrated.
You know that it’s not your fault.
But they don’t.
They believe that your code is defective and their code is perfect.
Moreover, all CI workflows are green on master, while your branch is red.
Who do we blame?
Obviously, you.
They won’t listen if you blame them for the failure in your pull request
You have to collect enough evidence and submit a bug report.
It should explain what’s wrong in their master branch.
Don’t ever mention your pull request.
It’s a trap!
If you try to use your pull request as a proof of their mistakes, they won’t listen.
Forget about your pull request for a while.
Submit a bug report as if you were a stranger who just found a bug in master.
It may be hard, since master is green.
However, there is no other way around it.
Again, they won’t listen if you blame them for the failure in your pull request.
At best, they will explain to you the basics of continuous integration.
4. Move On
Just as poker fish (immature players) try to win every hand, junior developers try to merge every pull request.
This is a mistake.
Some problems simply can’t be fixed and some features can’t be implemented right now.
There will be time for them later.
It’s perfectly all right to close a pull request after a negative review.
There’s no need to feel obliged to finish it.
It’s better to spend time on easier bugs to fix and simpler functionality to implement.
5. Don’t Call for Help
No matter how hard it is, don’t ask them to help you.
“I can’t understand why it doesn’t merge!”
Don’t say this.
They may help, but it will be annoying for them.
You won’t look like a reliable programmer.
The more you ask for help, the more you ruin your reputation.
You must know how to solve issues on your own.
A failed PR isn’t a setback.
It’s a lesson.
Fail fast, blame smart, and move on stronger.
Windows turns programmers into mouse operators. macOS, built on Unix, keeps the command line alive—where everything is a file, tools connect through pipelines, and real programmers stay in control.
Show full content
In 2020, in the Junior Objects book I wrote this:
“Windows is not suitable for programmers.
If you meet anyone who will tell you otherwise, you must know that you deal with a bad programmer, or a poor one, which are the same things.
Your computer has to be MacBook.”
Now, five years later, I still hold the same opinion.
This blog post is supposed to be less opinionated and, because of this, more convincing.
The point is still the same: you either use Windows or you are a professional programmer.
Das Experiment (2001) by Oliver Hirschbiegel
First things first.
This is what ChatGPT thinks about macOS vs. Windows (I toned it down a bit and sorted by importance, keeping what matters most at the top):
I can hear you saying:
What do I need it to be POSIX-compliant, and what is POSIX?
Why do I need grep, sed, and awk?
Am I a 60 years old Unix admin?
Why would I ever need git and make in the command line?
I don’t use command line at all.
I stay in the VS Code that works like a charm and helps me make a living.
I hear you. I do.
Now, hear me out.
You are not a programmer.
You look like one.
You walk like one.
You click the same buttons programmers click.
You even make the same salary they make.
But you are not one of them.
Yet.
Now, read on.
What Is Unix?
Programmers are the masters of computers.
They tell machines what to do.
To simplify the task of managing a complex hardware, programmers invented a few layers of abstractions.
The first layer is an operating system.
Instead of dealing with the hard drive and the pixels on the screen directly, programmers invented files and stdout.
They did it in the Bell Labs, during the late 1960s and early 1970s.
Earlier operating systems, like CTSS and OS/360, gave them a good start.
Unix was the first OS to say that everything is a file, including devices, directories, sockets, and processes.
They also invented pipelines and the philosophy: “Write programs that do one thing well, and work together.”
They also invented processes and their forking mechanism.
Five years later, another operating system was created, with different abstractions.
Not everything was a file anymore, processes were not parallel, and there were no pipelines.
The name of the system was CP/M and the name of the inventor was Gary Kildall.
Then, five years later, 24-year-old Tim Paterson has made a copy of CP/M and called it 86-DOS.
Microsoft purchased a non-exclusive license, rebranded it MS-DOS, and sold it to IBM.
That’s how Windows was born, in 1981.
Why were there no proper files, no processes, and no pipelines?
Because they weren’t trying to build a “real” operating system.
CP/M and MS-DOS were designed for tiny, single-user, single-task microcomputers, not multi-user minicomputers or mainframes.
Unix came out of Bell Labs—researchers, not hobbyists.
CP/M and MS-DOS were made for personal computers: offices and home users.
In other words, MS-DOS never meant to be a proper OS.
It was something that can boot up a small machine and run a single program.
Then, in 1985, Windows 1.0 was built.
It was a fancy GUI on top of MS-DOS, not a new OS.
Later, in 1995, Microsoft introduced 32-bit APIs (Win32) and preemptive multitasking.
However, the DOS subsystem was still lurking underneath.
Windows 95 looked modern but was still a half-DOS zombie.
At the same time, in 1993, the team of Dave Cutler has built Windows NT that was not based on DOS at all.
Latest Windows versions are descendants of NT, not MS-DOS.
Under the hood it’s conceptually closer to Unix than to CP/M.
There are features like protected memory, kernel/user separation, and file handles.
However, still it’s not Unix.
What Is macOS?
In 1984, Apple shipped their first Macintosh with the “System 1” operating system.
It was no better than MS-DOS: no multitasking, no memory protection, and primitive file system.
No surprise, it didn’t fly.
In 1997, Apple bought NeXT and adopted NeXTSTEP operating system.
They made it the foundation for the new Mac OS—codenamed Rhapsody, later “Mac OS X”.
In 2001 they shipped Mac OS X 10.0 (“Cheetah”).
Five years later I threw away my ThinkPad with Windows and bought my first MacBook with Mac OS X Leopard.
Modern macOS (Catalina, Ventura, Sequoia, etc.) is still built on that NeXT foundation.
It is POSIX-compliant and, of course, it has processes and pipelines.
In other words, it is Unix with a pretty GUI.
Abstractions
Both Windows and macOS, in their current versions, are solid operating systems.
The difference is in the abstractions inside them: files, sockets, processes, memory blocks, users, permissions, and so on.
In Unix (macOS), everything is a file, while in Windows, everything is an object.
Files in Unix are a uniform abstraction, that’s why they can be chained via pipes.
In Windows objects are not unified in practice, they have different interfaces.
This is why Unix shells and small composable tools became so powerful.
The uniformity of “everything is a file” made composition natural.
You can build complex workflows from simple programs.
Windows, on the other hand, evolved around GUI apps and message loops, not shell pipelines.
Pipelines
Unix was built around pipelines.
In Unix, everything is a small tool reading stdin, writing stdout.
At the same time, everything is a file, including sockets, devices, and processes.
Programmers, in Unix, see every process as a composition of smaller processes, glued together via pipelines.
This mindset, since 1970s, has proven to be effective, amongst a few generations of software engineering elite.
Say, you want to know which parts of your codebase change the most—maybe for refactoring, testing focus, or bug-hotspot analysis.
This is how you do it Unix-style:
Does this syntax make sense to you?
If it does, I bet you use WSL.
Most serious Windows developers end up doing exactly that.
The command line is the bare metal interface to Unix.
The heart of the command line is pipelines.
Thanks to pipelines, command-line tools are inherently composable.
You can chain them and automate tasks in seconds that would take hours by hand.
No IDE plugin can replace this power.
What Are You?
Now, you know what the difference is between Windows and macOS.
In both of them you can code, browse Internet, and watch movies.
However, in macOS you interact with the computer through Unix abstractions in a shell.
You don’t just use macOS—you inherit fifty years of disciplined abstraction.
In Windows you interact with the computer through draggable GUI elements.
A GUI makes you a consumer; a CLI makes you a creator.
A GUI hides the logic behind gestures and icons; a CLI exposes it as text you can reason about, automate, and combine.
You can’t pipe a button click into another program, you can’t grep a progress bar, and you can’t version-control a mouse movement.
Every click you make dies the moment you make it; every command you write can live forever.
Oh, wait.
In macOS you can’t really play games.
Bummer…
Maybe you shouldn’t, since you are a programmer?
In big organizations, reds obey bosses while blacks serve customers—and blacks survive only by keeping their distance.
Show full content
In Soviet Russia, prisoner camps were divided into two categories: red and black.
In a red camp, the power belonged to the prisoners who cooperated with the administration and helped it maintain discipline.
In a black one, the power was in the hands of criminals who resented the rules, in permanent confrontation with the administration.
It seems that in software companies we may also categorize employees into red and black.
A red employee enjoys obeying the rules and climbs up the career ladder by making the boss happy.
To the contrary, a black employee, while understanding the inevitability, despises the necessity to obey.
Their career growth is driven by making the customer happy.
Which one are you?
Холодное лето пятьдесят третьего (1987) by Александр Прошкин
An organization cannot exist without policies and regulations.
Especially if it’s a large organization where the percentage of underperformers is highest.
The clock-watchers must be disciplined so that they produce at least something.
Let’s assume you are not one of them.
However, even if you are not a nine-to-five clock-watcher, you can’t ignore the policies.
A few examples of the rules you may need to obey:
All reports must follow the official PowerPoint template
Every meeting requires written minutes sent to all participants
Job candidates must pass through the full HR pipeline
Employees must re-certify their skills on a fixed schedule
All of the above seem reasonable—just like prison rules.
The difference is your attitude toward them.
They are either a framework for your career or an inevitable evil.
You either feel happy when you do what’s required, or feel annoyed and do it just because it’s required.
You either fill out all the forms completely, or you cut every possible corner to get back to the real work.
You either read an email copied to 25 people to not miss anything or immediately delete it.
You either attend all meetings or learn to be creative in finding reasons why you can’t.
You are either red or black.
Once you find your identity, you start despising the other side.
Minimize contact, keep your head down when necessary, and focus on delivering real value where it matters
If you are red, you don’t understand the black employees who are constantly rebelling.
They rebel against the rules you so much enjoy following.
You see them as dishonest, disloyal, and selfish sociopaths.
If you are black, you despise the red employees who are trying so hard to associate themselves with the system.
They study the rules and compete against each other—who knows them better.
You see them as shallow and incompetent impostors.
Peace is not possible.
In any large organization, reds will always dominate—it’s inevitable.
The system needs them, and they thrive inside it.
Blacks will always be a minority, surviving at the edges.
If you are black, don’t waste energy trying to convert or fight the reds.
You won’t win.
The only strategy is distance.
Minimize contact, keep your head down when necessary, and focus on delivering real value where it matters.
Reds will spend their lives climbing the corporate ladder.
Blacks must learn to survive in the shadows of that ladder, working for the customer, not the boss.