Beware of the Title Chasers: How to Spot Power-Seekers in Tech Teams

Every engineering organization has ambitious people, and ambition is healthy. Wanting to grow, lead, or take on more responsibility is a natural part of a career. But there is a different type of person that is worth recognizing early: the one who is not chasing growth, but chasing position.

What the pattern looks like

The title chaser is rarely driven by the work itself. Their energy goes into climbing, not building. Common traits include:

  • Weak craft, strong ambition. Their technical or professional track record is often thin. Instead of deepening their skills, they look for a role where skill matters less than control.
  • Problems are currency. Rather than solving issues, they tend to generate, amplify, or prolong them, because a team in crisis needs someone to “take charge.” A calm, well-functioning project gives them nothing to rescue.
  • Psychological levers instead of arguments. When they cannot win on technical merit, they rely on guilt-tripping, veiled threats, emotional pressure, or making others feel responsible for failures they did not cause.
  • Hierarchy as identity. They constantly signal rank, often through language. Phrases like “back when I was just a lowly frontend developer” or “my devs…” are quiet markers: the first positions the speaker as someone who has risen above the craft, the second frames people as possessions rather than colleagues.
  • Credit upward, blame downward. Successes become personal achievements; failures belong to “the team.”

Why they are dangerous

The damage rarely comes from a single incident. It accumulates.

Toxicity spreads. Teams adopt the behaviors that get rewarded. When manipulation and posturing are tolerated or promoted, others learn that they work.

Similar people gather. Power-seekers tend to surround themselves with people who share their approach, or who are easy to control. Over time, a small circle forms where loyalty matters more than competence.

Good people leave. Skilled, independent professionals are the first to notice the dynamic and the first to walk away, because they can. What remains is a team that is easier to manage but weaker to deliver.

Projects suffer. Decisions are made to protect status rather than to serve the product. Risks get hidden, honest feedback disappears, and technical debt grows because raising problems becomes personally dangerous.

Warning signs to watch for

  1. They are more interested in who owns a decision than in whether it is right.
  2. Conflicts seem to follow them from team to team.
  3. They rely on private conversations, shifting narratives, and selective information.
  4. Feedback is taken as an attack, and critics are quietly sidelined.
  5. Their own technical contributions are hard to point to, yet their influence keeps growing.
  6. They get uncomfortable when others become competent or visible.

No single sign is conclusive. A pattern across several, sustained over time, is.

What to do about it

For individual contributors

  • Document important decisions and agreements in writing. Clarity is hard to manipulate.
  • Do not accept guilt or blame that is not yours. Ask for specifics: what happened, what evidence, what outcome is expected?
  • Build relationships across teams so that you are never dependent on one person’s opinion of you.
  • Know when to leave. No project is worth your professional health.

For managers and leaders

  • Evaluate people on outcomes and on how those outcomes were reached. A “high performer” who leaves a trail of burnt-out colleagues is not a high performer.
  • Pay attention to attrition. Repeated departures from one person’s team are data.
  • Promote based on demonstrated skill and collaboration, not on confidence and self-promotion.
  • Create safe channels where concerns can be raised without retaliation.

For organizations

  • Make technical career paths as rewarding as management paths, so that leadership is a choice for those who want to serve a team, not an escape for those who cannot grow technically.
  • Treat psychological safety as a delivery factor, not a soft perk.

A necessary caution

Not every manager is a power-seeker, and not every difficult colleague is toxic. Leaders who came up through the ranks and mention their past do not automatically fit this pattern. The difference lies in intent and behavior: a healthy leader uses their experience to help others grow, while a title chaser uses it to elevate themselves. Judge by actions, consistently observed, rather than by single phrases or isolated bad days.

Final thought

Great teams are built on trust, competence, and honest communication. Those who chase titles for the sake of power erode all three. Recognizing the pattern early, and refusing to reward it, protects both the people and the products they build.

When the Technology Doesn’t Fit: Rescuing a Platform Buried Under Workarounds

A story from the field, with names changed to protect the innocent.

A few years ago, we were brought in to help a client whose platform had a reliability problem. Almost every week, the site went down, and the cause was usually the same: the database buckled under load. Every new feature took longer to ship than the last, incidents were hard to diagnose, and each new batch of users made things worse.

The root cause wasn’t a weak team or a bad product idea. It was a technology choice made years earlier, and everything that had been built around it ever since.

The setup

The platform had been built from scratch by an independent consultant, whom we’ll call Mark X. He built it on XSpaces, an open-source component framework built on JSF and publicly available. What made the choice unusual is that Mark had written XSpaces himself. Nobody knew the technology better, and for a while the platform did what the client needed.

But a technology can be well crafted and still be the wrong one for the job. This platform needed to grow, and its foundation wasn’t built for that.

What we found

  • The stack was heavy to write, read and maintain. Even simple changes meant navigating layers of component abstractions and configuration, which made the code slow to understand for anyone who hadn’t lived in it for years.
  • The architecture didn’t scale with users. JSF’s server-side component model keeps a lot of state per session, and the way the application interacted with the database put growing pressure on it as concurrency increased.
  • Workarounds were piled on workarounds. Whenever the platform hit the limits of the framework, the answer was a patch. Over the years these patches became load-bearing: some compensated for the framework’s shortcomings, others for the side effects of earlier patches, and some of them added to the very database load that was causing the outages.

The result was a system that fell over almost weekly, where changing one thing risked breaking three others, and where every incident was a firefight rather than a fixable problem.

The human side

This wasn’t purely a technical story. XSpaces was Mark’s own creation, and the platform was built on it, so he was understandably protective. Suggestions to simplify or move away from parts of the stack were often met with resistance, because when the technology is your own work, every critique of it feels personal.

We tried to approach it with empathy. The choices had made sense at the time, and he had kept the platform running for years. But the risk to the client was real: a critical system whose inner workings, and all its accumulated workarounds, lived mostly in one person’s head.

After a few years, Mark moved on, and that’s when the real work began.

Taking over

A handover like this is never clean. Our first priority wasn’t to rewrite anything, but to stop the bleeding and understand what we had.

  1. Reduce the outages. We started with the database: finding the heaviest queries and access patterns, fixing the worst offenders, and adding monitoring so we could see what the platform was really doing under load.
  2. Map and document. We traced the critical flows end to end, separated essential workarounds from historical accidents, and wrote down what had only existed as tribal knowledge.
  3. Add safety nets. We put safeguards in place around the most critical flows, so we could change things without fear.

The phase-out strategy

A full rewrite was tempting, and it would have been the wrong call. The business depended on the platform, and big-bang rewrites have a long record of running late and over budget.

Instead, we designed a gradual phase-out of the JSF-based stack, in the spirit of the strangler fig pattern:

  • New functionality was built on a simpler, more mainstream architecture from day one, so the legacy part stopped growing.
  • Existing modules were migrated one at a time, ordered by business value and pain rather than technical elegance.
  • Workarounds whose reason for existing had disappeared were removed instead of ported over.
  • The overall architecture was simplified, with fewer layers, clearer boundaries and less hidden state, and a much lighter footprint on the database.

Throughout, the platform stayed live and users kept working.

The results

Over time, the platform went from fragile to dependable:

  • Incidents went from almost every week to once every few months.
  • It can handle far more concurrent users, with much more headroom than before.
  • Maintenance is predictable, and new developers get productive much faster on a simpler codebase built on mainstream technology.
  • The client no longer depends on any single person to keep the system running.

What we took away from it

Choose technology for where the product is going, not just where it is today. A stack that works for hundreds of users can quietly become the bottleneck at thousands.

Be wary of niche technology, even when it’s open source. Publishing a framework doesn’t create a community around it. If the only real expert is the person who wrote it, the bus factor is still one.

Recurring incidents are a signal, not a fact of life. If the platform goes down every week, the fix is rarely another patch. It’s usually the architecture.

Workarounds are debt with interest. Each one is cheap to add and expensive to live with, and eventually someone pays the bill.

Migrate incrementally. Evolution beats revolution when the system has to keep running.

Treat the people with respect. Legacy code is never just code. Someone made those choices under real constraints, and the best rescues honor that while still moving forward.

At bitGloss, this is the kind of work we enjoy: stepping into a complicated situation, understanding it before judging it, and leaving the client with a platform that’s simpler, faster and easier to own. If your team is dealing with a system that has become harder to change than to live with, we’d be glad to talk.

Cum a apărut worklog.ro: un tool intern care a devenit produs

La bitGloss nu ne-am propus niciodată să construim un produs doar ca să avem un produs.

worklog.ro a apărut dintr-o nevoie foarte practică: să ne organizăm mai bine munca, să urmărim timpul investit în proiecte și să putem genera rapoarte fără să ne adaptăm permanent după limitările altor aplicații.

Și, ca multe produse bune, a început ca un tool intern.

Problema pe care încercam să o rezolvăm

În teorie, problema era deja rezolvată.

Existau și există multe produse mature pentru time tracking și project management. Am folosit și noi astfel de soluții. JIRA era prezent în ecosistem, iar o perioadă am folosit și YouTrack instalat pe infrastructura proprie.

Dar apăreau constant mici probleme:

  • unele fluxuri de lucru nu se potriveau modului nostru de lucru;
  • raportarea nu era suficient de flexibilă pentru ce aveam nevoie la acel moment;
  • modelul de cost nu era întotdeauna convenabil;
  • trebuia să combinăm mai multe unelte pentru ceva ce, în esență, voiam să fie un proces unitar.

Nimic dramatic.

Doar suficient de multă fricțiune încât să ne întrebăm: dacă tot adaptăm procesele după tool, de ce să nu construim exact ce ne trebuie?

Așa a apărut prima versiune worklog.

Prima versiune: strict pentru noi

Inițial nu exista intenția să devină produs.

Aveam nevoie de ceva simplu:

  • logare de ore;
  • organizare pe proiecte;
  • generare de rapoarte;
  • administrarea accesului pentru echipă.

Frontend-ul inițial a fost construit în ClojureScript.

A funcționat bine și ne-a permis să iterăm rapid. Dar pe măsură ce aplicația a crescut și utilizarea a devenit zilnică, am început să observăm un lucru important: frontend-ul era întreținut de o singură persoană.

În acel context, viteza de dezvoltare conta, dar controlul calității conta și mai mult.

Așa am decis să migrăm frontend-ul către Elm.

Motivul nu a fost „tehnologie nouă” sau dorința de rescriere. Motivul a fost unul foarte pragmatic: Elm mută foarte multe categorii de erori în etapa de compilare. În practică, asta înseamnă mai puține bug-uri care ajung în producție și mai puțin efort de validare manuală.

Pentru o aplicație întreținută eficient de o echipă mică, asta s-a dovedit o alegere foarte bună.

Câțiva ani de utilizare reală

Nu există test mai bun pentru un produs decât să depinzi de el.

Ani la rând am folosit worklog exclusiv intern.

Fiecare neajuns ieșea imediat la suprafață.

Dacă un raport dura prea mult să fie generat, îl simțeam.
Dacă lipsea o funcționalitate, apărea rapid în backlog.
Dacă ceva era greoi, nimeni nu evita problema.

În perioada aceea am trecut prin mai multe iterații și am rafinat produsul continuu.

Momentul în care am renunțat la Excel

Până în 2021, facturarea noastră se făcea încă prin Excel.

Funcționa.

Dar doar în sensul că orice proces manual „funcționează” până când devine suficient de enervant.

Generarea facturilor era repetitivă, predispusă la greșeli și consuma timp inutil.

În 2021 am generat prima factură direct din worklog.

A fost unul dintre momentele în care am realizat că produsul începe să rezolve mai mult decât problema inițială de time tracking.

Pur și simplu era mai simplu.

Legislația a schimbat direcția produsului

Între timp, cadrul fiscal din România s-a schimbat.

A apărut necesitatea integrării cu SPV și e-Factura.

În loc să tratăm asta ca pe încă un tool separat, am ales să integrăm fluxul direct în platformă.

Astfel, worklog a început să lege natural:

ore lucrate → raportare → facturare → transmitere.

Când ne-am dat seama că nu suntem singurii cu problema asta

După ani de utilizare internă, a devenit clar că modelul nostru nu era deloc unic.

Existau mulți oameni și multe firme care funcționau similar:

  • freelanceri;
  • PFA-uri;
  • SRL-uri mici;
  • agenții;
  • echipe care facturează la oră;
  • firme care lucrează atât cu clienți români, cât și internaționali.

Toți aveau nevoie de aceleași lucruri:

  • time tracking;
  • rapoarte;
  • facturare;
  • integrare cu cerințele fiscale;
  • administrare fără complexitatea unei platforme enterprise.

Atunci am decis să deschidem worklog.ro și către exterior.

Nu ca idee de startup.

Ci ca produs validat deja prin ani de utilizare internă.

Funcționalități care au apărut din utilizare reală

Pe parcurs am adăugat funcționalități care au rezolvat probleme concrete:

Integrare GitHub
Posibilitatea de a loga ore pornind de la commit-uri extrase direct din GitHub.

Integrare JIRA
Logarea timpului pornind de la tichetele existente.

Arhitectură multi-tenant
Fiecare cont are:

  • un administrator;
  • unul sau mai mulți utilizatori;
  • proiecte separate.

Administratorul este și el utilizator — poate loga ore ca oricine altcineva — dar are responsabilități suplimentare:

  • generare facturi;
  • management proiecte;
  • management utilizatori;
  • asocieri între utilizatori și proiecte;
  • controlul accesului în diferitele module.

Modelul a fost construit după cum funcționează firme reale, nu după cum arată un demo.

De ce publicăm povestea asta

Există multe produse care promit multe.

Noi preferăm altă abordare.

worklog.ro nu a fost construit pentru a intra pe o piață.

A fost construit pentru că aveam nevoie de el.

Faptul că astăzi este disponibil și pentru alții este doar continuarea firească a unei aplicații pe care am folosit-o ani întregi înainte să o prezentăm public.

Is Your Tech Stack Appropriate or Are You Just Trying to Be Fancy?

When a software project starts struggling, the first instinct is often to blame the code.

But in many cases, the real problem sits one level higher: the architecture and technology choices themselves.

We’ve seen systems built around microservices long before they had enough scale to justify them. We’ve seen multiple frontend frameworks living in the same application. We’ve seen message queues, event buses, service meshes, and orchestration layers solving problems that didn’t actually exist.

The result is predictable: development slows down, debugging becomes harder, onboarding takes longer, and every new feature costs more than it should.

The challenge is that it’s difficult to evaluate an architecture from diagrams and presentations alone.

We Don’t Start With Opinions

Before we assess your technology stack, we need to understand it.

That’s why we don’t begin with a slide deck full of recommendations. We start by working inside your system.

We fix bugs. We implement features. We deal with the same constraints your team deals with every day.

This hands-on involvement gives us a realistic understanding of how your application behaves in production, how your development process works, and where complexity is helping versus hurting.

Only after we’ve worked within your codebase do we form conclusions.

The Questions We Try to Answer

Is your Spring microservices architecture genuinely supporting your scale and organizational needs?

Or would a simpler modular monolith allow your team to move faster?

Are those message queues providing real business value?

Or are they introducing operational complexity that could be replaced by straightforward API communication?

Do you actually need multiple frontend frameworks?

Or are they creating fragmentation, duplicated knowledge, and inconsistent user experiences?

Most importantly:

What complexity is essential, and what complexity is self-inflicted?

An Honest Technical Assessment

After gaining real experience with your system, we’ll give you a straightforward assessment.

If the current architecture makes sense, we’ll tell you.

If parts of it are over-engineered, we’ll explain exactly why.

We’ll identify components that can be consolidated, technologies that can be removed, and areas where complexity can be reduced without sacrificing business capabilities.

Our recommendations are practical and grounded in experience with your codebase—not architectural fashion trends.

Simplicity Is Often a Competitive Advantage

Technology teams rarely suffer because they have too little complexity.

More often, they suffer because complexity accumulates faster than business value.

Every service, framework, integration point, and infrastructure component carries a maintenance cost. Over time, those costs compound.

The best architecture is rarely the most sophisticated one.

It’s the one that solves today’s problems while remaining easy to understand, maintain, and evolve tomorrow.

That’s what we help our clients achieve.

Not more technology.

The right amount of technology.


Every architecture decision has a cost.

If you’d like an honest assessment based on real hands-on work – not diagrams – we’d love to hear about your project.

bitgloss.ro

Making Java code more readable with Type Abstractions

Let’s consider a simple example project, appointments, where each appointment has a date, doctor, patient, and comments. These records are read from input, displayed in list or tabular form and saved. The code involves multiple layers — reading and writing I/O, formatting data, and writing formatted views.

Here’s the actual Java representation of an appointment:

public record Appointment(LocalDate date, String doctor, String patient, String comments) {}

Early in the design, many of the functions that manipulate these appointments had verbose, deeply nested type signatures like BiFunction<List<String>, Supplier<Stream<List<String>>>, String>. While correct, such signatures obscure intent. That’s where type abstractions come in — small, domain-specific type aliases that make the structure of the code clearer and more readable.

The Problem: Overly Complex Function Signatures

Consider this example, which takes a list of headers and a content supplier, producing a string representation of a list view (the table view is omitted for brevity) :

public static BiFunction<List<String>, Supplier<Stream<List<String>>>, String>
listFormat =
    (headers, content) ->
        header(headers).append(data(content)).toString();

This works, but the type signature is long and opaque. It doesn’t immediately tell you what the function is about — only that it’s a BiFunction of lists and streams that returns a string.

The Solution: Creating Semantic Type Aliases

Java doesn’t have native type aliases, but we can emulate them through interfaces that extend existing types. For instance, we can define a View type that captures the concept of a function that takes headers and content, and returns a formatted string:

public interface Types {
    interface View extends BiFunction<
            Collection<String>,
            Supplier<Stream<Collection<String>>>, String> {}
}

With this, our previous function becomes much cleaner:

public static View listFormat = (headers, content) ->
  header(headers).append(data(content)).toString();

The functionality hasn’t changed — but the intent is now explicit. We’ve moved from a generic, mechanical type to a domain-level concept: a View.

Extending the Abstractions

Next, the display function — responsible for rendering appointments — takes a View (the formatter) and returns a ViewWriter<IO> (the executor that writes the formatted output to an IO stream). Originally, its signature was difficult to read:

public static Function<
    BiFunction<List<String>, Supplier<Stream<List<String>>>, String>,
    BiFunction<List<String>, Supplier<Stream<List<String>>>, Consumer<IO>>>

display = ...

Using type abstractions, this becomes far more expressive:

public static Function<View, ViewWriter<IO>> display =
view ->
  (headers, content) -> io ->
    io.print(content.get().count() == 0 ? "No appointments found\n"
      : view.apply(headers, content));

Here’s how the types break down:

  • View — the formatter: takes headers and content, produces a string representation of the table.
  • ViewWriter<W> — the executor: takes headers and content, and produces a side effect by writing the formatted string to a consumer of type W (like IO).
interface ViewWriter<W> extends BiFunction<
        Collection<String>,
        Supplier<Stream<Collection<String>>>,
        Consumer<W>> {}

This separation keeps formatting logic distinct from side-effect logic: View handles what the output looks like, and ViewWriter handles where it goes.

Abstracting Read and Write Operations

Finally, the function responsible for reading a new appointment from input and writing it is defined as:

public static ReadWriter<Appointment, TypedIO> addNew =

    writer ->
      reader ->
        writer.accept(new Appointment(
            reader.readDate("Enter date: ", "invalid date"),
            reader.readString("Enter doctor: ", "").orElse(""),
            reader.readString("Enter patient: ", "").orElse(""),
            reader.readString("Enter comments (if any): ", "").orElse("")));

Its type alias is equally simple and expressive:

interface ReadWriter<R, W> extends Function<Consumer<R>, Consumer<W>> {}

Rather than dealing with nested Function<Consumer<X>, Consumer<Y>> constructs, we now have a ReadWriter — a function that connects two side effects: reading and writing.

The Result

These abstractions don’t change the runtime behavior of the code. They change its shape.
Now, a developer reading Function<View, ViewWriter<IO>> can immediately tell that the function takes a view and returns a view writer — no decoding of generic types required.

The result is clearer, safer, and more expressive code. Type abstractions let us represent not just data, but also intent, in the type system. They make it easier to reason about functions, compose behaviours, and test side effects in isolation — all without adding boilerplate or runtime overhead.

(Adapted from Chapter 3, “Type Abstractions,” in Dr. Software, available at bitgloss.ro/dr-software.pdf

Full example code found here)

Artificial Intelligence: A misleading name for powerful tools

This article is a follow-up of my 2019 article on the same topic, but updated in the light of today’s AI hype.

Like I mentioned then, when I first experimented with neural networks years ago, I was struck by how little “intelligence” there actually was in them. They did exactly what I coded them to do—no more, no less. Today’s systems may look more impressive, but the principle hasn’t changed: these are tools, not minds.

The Problem With the Word “Intelligence”

Humans don’t even have a precise definition of their own intelligence or consciousness. If we can’t define those terms for ourselves, it makes no sense to claim that we’ve created them in machines.

What we can say is that current AI systems don’t exhibit the qualities most people intuitively link with intelligence: self-awareness, understanding, intent, or meaning. They generate outputs by compressing patterns in data, not by reasoning or knowing.

To call that “intelligence” is to confuse statistical mimicry with cognition.

What AI Actually Does

Modern AI systems, especially large language models and generative tools, are best understood as:

• Pattern machines. They excel at finding correlations in vast amounts of data.

• Automation engines. They can handle repetitive, data-heavy tasks quickly and consistently.

• Amplifiers. They extend human capability, but only within boundaries set by training data and design.

This is powerful, but it is not thought.

The 2025 Reality Check

• Scale isn’t sentience. More data and bigger models don’t bring us closer to human-like understanding.

• Usefulness ≠ understanding. A tool can be highly practical without being intelligent.

• The real risks are human. Bias, misuse, privacy abuse—these are problems in how people deploy the systems, not evidence of AI “deciding” anything.

Why the Distinction Matters

If we keep pretending AI is a kind of mind, we risk treating its outputs as if they were grounded in meaning or truth. They aren’t. They’re grounded in probabilities.

AI is not intelligent because we don’t even know what that word would mean in this context. What we do know is that these systems are fundamentally different from human thought: they calculate, predict, and generate—but they do not understand.

Conclusion

The danger isn’t that AI will “wake up.” The danger is that humans will forget what it actually is: computation dressed up in human-like outputs. Powerful, yes. Useful, yes. But never a mind.

Automation

A term thrown randomly in lots of software development shops, to describe different things. Why? Automation is a simple term, that used to mean a process that can run without (or with minimal) human intervention. Now it means lots of things and, in most cases, it’s a lie. Let me explain what I mean, through a software development skit:

Alice: Bob, where do we stand with the QA automation for this release?

Bob: We’ll probably have to modify 40% of the regression suite, ’cause lots of tests are failing.

Alice: Do you think we’ll make it in time?

Bob: We could bring in 5 guys from John’s team to help.

Alice: Sounds like a good idea. Let’s do that!

No one ever…

I’m sure you’ve never seen this in practice, ever… Right? “Good idea” what!??? Of course, Alice is acting purely based on her project management targets, but she’s completely missing out on what automation should do for them. That is: reduce the intervention of humans in the process and get faster feedback from that regression suite (as opposed to people playing keyboard monkey roles).

What happened back there? Well, 40% of 10 tests is nothing, so bringing in 5 guys would be one person per test that could help, plus an extra one. The problem could be solved in a matter of minutes…? maybe hours…? Ok, good call Alice! But the elephant in the room, in this scenario, is that the regression suite is actually a joke, not real automation. I mean 10 tests? (yes, I just decided that there were 10 tests, don’t scroll up)

The problem in the large, is that the regression suite has hundreds, if not thousands of tests. This means feedback comes late (hours, sometimes days) after running the suite. Feedback is not clear most of the time, false positives, flakiness and all that good stuff.

Alright. QA automation. But there are other kinds, right? Oh, sure there are. Let’s take the famous “dev-ops pipelines” for example. “What does that even mean? Dev-ops pipeline, pfff…” you say? Stop pretending and just accept you already know what I’m talking about: pull source from GIT onto some jenkins machine, run some stuff from some other jenkins machine and then some stuff on this jenkins machine and then “promote” the buid to some other machine and chop a virgin’s head and make sure all is being pushed to Kibana and kill yourself.

Isn’t that THE automation? Well yes, it is, it’s just that… more often than not it goes wrong at different steps in the process. When was the last time you’ve seen a pipeline like this run well for weeks or months at a time, without human intervention. I can already hear the audience… “months? we’re stepping in a couple of times a day”.

We can see a pattern here: “automation” that hurts rather than helping. Ok, by “hurts” I mean that it does so for the business goals: late deliveries, buggy software products, etc. It totally does NOT hurt the persons which created the “automation” to begin with. To them, it’s an industry standard and if you’re not doing it, you’re so not worth living and must crawl with shame under your desk (not standing desk, of course, ’cause you’re a loser and you’re not trendy). They will defend it to their deaths (that is, until they move to a more trendy company, with bean bag chairs and super-expensive espresso machines).

There are many other kinds of “automation” which generate similar results to the above. Bottom line is: if it constantly hurts the business goals, you’re doing it wrong. “Yes, but my special case…”. NO! Just throw it away (YES, away it should go) and rethink the whole thing. I mean really rethink it in such a way that you won’t have to touch it afterwards, not just use a different technology to do the same thing all over again. It will probably take a bit of time (weeks, months…), but the business will certainly thank you for it.

P.S. What are you doing when you give business a software product? You automate its processes. Why? So they don’t have to do it manually. That business may just as well be you.

Stop mocking your system!

Yes, mocking has a mostly negative meaning! Only this – to imitate (someone or something) closely – doesn’t feel negative.

Mocking is usually used in testing code and it looks something like this:
We have module A which uses modules B and C. When we write tests for module A, we don’t initialise it with the real B and C, but rather “mock” B and C and hand those mocks to A.

All of us have used mocks in our testing code, to substitute the real things. I know I’m definitely guilty of this. Why though? Why do we do it? Well, It’s usually because initialising those collaborators is not trivial (i.e. can’t be done with a one-liner).
But why wouldn’t the collaborators be easy to spin-up? Well, because they have their own issues with their own collaborators and so, you see, the design issues start to emerge. This is why you read things like “TDD makes your design” better (I’m not going to start this now… maybe in a future article).


But let’s backtrack a little bit and be honest with ourselves. What we’re actually doing is faking (please don’t start with the stub/fake flamewar) things that we’ve created ourselves. We lie to ourselves saying that we’re only testing one thing at a time, so having two or more real things breaks best practices. Which best practices? Stack overflow’s most voted answers? Check this out: you set your test victim with mocks and boom, now you have two of the same collaborators living in your codebase. And guess what… you have to keep the mocks in sync with the real things, in case you thought otherwise. Don’t worry, you’ll forget to do that once in a while (I’m being gentle here…) and you know what the end result of that is? Yes, yes, the all too familiar “green tests broken product” syndrome. You know… a dev and a QA walk in to a bar. QA says: “it doesn’t work”. Dev says: “it must be working, all the tests pass”. All the dev tests that is…
And this is exactly where the heart of the problem lies! Creating mocks is just creating an alternative universe for your system, that will eventually lose all connections with reality.
What you almost always want, when mocking, is really just different input data for your module, data normally provided by a long stream of collaborators. Just build your program in such a way that you can send it this data, regardless of the runtime (unit test framework, test env or production env). Yes, it is possible and highly desirable and don’t be in denial right now.

But dude, I don’t want to use a real database (or AWS endpoint or rocket launcher) in my tests. Debatable, but fair enough. Simulating 3rd party systems is acceptable when not doing so would lead to bad consequences in a production environment. Key concepts here are “3rd party” and “production”. If using a 3rd party production system won’t hurt anyone, just do it. Being as close to reality as possible is the best thing to do. Anything else is just a web of lies that we have to maintain.

Mocking your own system is just mocking your own reality. Stop lying to yourself!

Cargo cult programming

…or how to create huge useless programs, that is. In all fairness to the folks doing this, it’s the best they can do, given the knowledge they hold. And this is where I want to make my argument, but first…

Cargo cult programming, says Wikipedia, “is a style of computer programming characterised by the ritual inclusion of code or program structures that serve no real purpose”.
Let’s expand on that a little bit. How can there be code that serves no purpose? What is the purpose? Good questions!

The purpose of the code is to respond to every need of the product. Pretty simple, right? Right! It’s the every need of the product thingy that’s sort of vague and, frankly, it’s not easy to determine EVERY need of the product. There are lots of variables in this domain, some of which are: user preferences, product usage patterns, user base growth, product runtime infrastructure changes, product developers change rate etc. And I mentioned nothing about actual code yet! That’s right, because before having actual code, we need to determine a model for all those variables mentioned before. “Architecture” I hear you say? Whatever… I don’t care how you call it as long as there is some good thinking done first, to address that model.

Please, oh please don’t get hung up on words like: architecture, model etc. This is exactly where cargo cult programming (CCP henceforth – it would have been funny to have another C in there, before the P) stems from.

I’ll give you a method to identify whether or not CCP is employed in your project/product:
Talk to a senior programmer who’s working on the product. Ask them to explain in detail, a small part of the product. Yes! In detail! Tell them to show you the classes (because OOP is almost certainly what you’ll find) and explain how and why they are organised like they are. Take your time, be patient and do your best to follow the explanation. And now the Aha! moment: if the programmer is not eloquent and you don’t understand the explanation or it doesn’t make sense, chances are you’ve got yourself a little CCP going on.
Everything is explainable in layman’s terms… if you understand what you explain, that is. Oh and beware of the “best practice” expression. It usually means: “others on the web are doing it like this and it means this is the way to do it”. So usually “best practice” = CCP (of course it shouldn’t, but that’s what it usually hides behind)

Another way to spot CCP would be to ask programmers what literature they got the ideas from, at which point, more often than not, they would quote websites, articles and blogs (that’s right! You better not be learning programming from this blog!). What you need to hear is books and good authors (e.g. Kent Beck, Robert C. Martin, Martin Fowler, Michael Feathers and many more).

So, CCP is a BAD thing. Repeat after me: CCP is a BAAAD thing. It makes programs less maintainable, more buggy, more expensive. That’s it!

All right. Enough chit-chat! I’ll let you get back to your Wikipedia binge-clicking. Cheers!

Trade-off

Another term that is being overused and misused across our industry… Ok, I want to clear its name a little bit and explain how we actually do trade-offs every day.

Consider this scenario: You need to take over a big legacy system. Really poorly written, no tests, coupling everywhere. You are required to add a couple of minor features and fix a few old bugs. You, you’re a professional programmer, with a proven track record. “They” are product owners which would like those features and bug fixes in production ASAP. Users are demanding justice!

You are faced with a dilemma: How can I, THE programmer, deliver this in an impossible time frame? It’s crazy! It’ll never work… I need to isolate the parts where I’ll have to add those features, I need to write tests to make sure I’m not breaking stuff, I need to refactor the code, to understand it better now and later… all these things, the humanity!!! And what the hell is a m_hWnd pointer?!

In the face of this apocalypse, you might crumble and cry, or you might hit “them” hard until they leave you alone and slowly move you to a “green field” project nobody uses, or… you may consider the following aspects to this problem:

1. The application is in production for years and it produces $$$.

2. Users are pushing.

3. The code is shit.

4. The new features and bug fixes are not really that big.

Now, should you hold your own and tell everyone about point 3, crying how this should inevitably be rewritten and that will take months, years, omg, the humanity!!?? (really author… again with this?) Or instead, should you realise that there is no way in hell you can rewrite this in a timely fashion and just slap some more shit on top, to get it over with and crash like a diva ninja-developer on a chaise longue asking for refreshments?

Well… (get ready for it…) it depends! This is it, thank you folks, good bye!

Wouldn’t you really hate my guts if I ended it there (like some professionals often do)? Hahaha. No my friend. I’m here to help. Here’s what’s gonna happen: you’re going to realise that indeed, you don’t have the time to make it all nice and shiny. But! You won’t slap shit on top of it. You’ll make it so that the new stuff you write is nice and tidy, with tests and all that, but linking it to the legacy system will probably be a little shitty, yes. There’s your first trade-off. The bugs? Well, the bugs get sprayed, but the code defects, you’ll track down with the debugger (ooh, watch out TDD purists), understand what’s going on and apply a small local fix. Second trade-off.

Now, will the code be better after this? Mostly no. But the new parts you wrote will be good and overall, the system will satisfy the angry user mob.

What about your previous articles about clean code and refactoring and all that good stuff (a bit of narcissism, I agree)?

Well, they are part of what I described above too (remember? the “new stuff” you added to the legacy crap). Funny thing is, we expect other people to do these kind of trade-offs every day. Imagine the scenario above, but with a little alteration: you take your car to the mechanic for a couple of small tune-ups and some scratch fixes. Of course you want your car back ASAP and with those things done. What do you think the mechanic does in his little magic castle? Why trade-offs, of course. You get your car back, with the stuff you asked for, you use it, there you go…

So back to the term. Trade-off. It’s actually a placeholder for judgement. The more you think about trade-offs, the more judgement you apply to the problem at hand. So there it is, I guess that’s what I was trying to say.