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.

Quality in software development

What does this truly mean? Somebody actually told me, during a business talk, that quality was just a buzzword. It felt wrong, but in a way it felt real too. I now understand where he was coming from and I can explain it. The only way he experienced quality in software development, was through the word itself: quality. The context around it was usually messy (code, defects, unhappy clients etc.). No wonder he started hating the word and sarcastically referring to it as a buzzword.

So back to the question: what does quality in software development really mean? There is more than one point of view that we should explore, so here we go:

The big picture

Ultimately, everyone wants to get what they ask for, without any hassle and within their budget. The holy grail is getting the best thing, for free, right away. Don’t try to fight this. It’s true. Think of the best version of anything you want and tell me you wouldn’t like it materialising in front of you, right now. So from this point of view, quantification of quality is done on the scale of “this is not what I wanted and it’s too expensive and it takes too long to get” to “this is exactly what I wanted and it’s free and I got it“.

Of course, there are some variations, one of which is heavily encountered in the industry: “this is not quite what I wanted, but it’s cheap, yet it took too long to get“. These are the murky waters many clients jump into when having to do trade-offs. In my experience, these are usually budget trade-offs, that end in an unfortunate state, where over-budget gets spent in the same bucket, but for patch work.

This is the scenario I like best:

Client: This is my problem. Can you solve it?

Provider: Yes.

Client: What will it cost me, in time and money?

Provider: I estimate around 6 months and 500.000$

Client: Great! Let’s do it!

(every couple of weeks)

Provider: Is this what you had in mind?

Client: Almost. Here, I’d like a little more blue.

Provider: Done!

(around 6 months later)

Client: It looks like we’re done. Great job! Thanks!

Provider: Anytime.

There are lots of details behind this scenario, but this is the big picture. When I read it, it gives me a peaceful feeling of “yes, this is how business should be“.

Processes

These are blueprints of behaviour that help when dealing with certain problem patterns. They also help the entities that participate, have some (illusion of) predictability of the future. It’s a sort of guarantee that the future will happen in a certain way. This helps people feel in control and reduces the level of stress. BUT! Since we derive a level of comfort from having the illusion of a certain future, we tend to equate the process with the future itself. This is a mistake that leads to process rigidity and reluctance to step outside its bounds.

I like to look at processes as checkpoints and guidelines along the road. The main focus should always be the product. Engaging my expert skills right now, while actively building it, IS the best guarantee I can have of the future I want. The future is built from a constant stream of present 🙂

I always look at the reason for the existence of the process, so it gives me a better understanding of why it was set up in the first place. This way I can work along the lines of that reason, rather than blindly following a process I cannot understand (and sometimes come to hate).

One thing I noticed is that the fuzzier the goal, the more processes there are. The more processes there are, the more people tend to make a goal out of them. And so the wheel spins…

Craft

This is, surprisingly, not the most looked at factor in the software development industry. In my experience, if a client sees processes and pays an amount that suits her, THE CODE (which is ultimately the product) doesn’t matter as much, as long as it… exists.

WHAT??!! This is outrageous! But wait… is this something the client should even be aware of? Why would it be so? If I buy a pair of shoes, I’m not interested in the way they are built, what I’m really interested in is how they look, how they feel and how long they last.

The craft IS really important, inside the guild. Because we’re talking about code, things should be simple, really: it should work as required (without maintenance, preferably) and it should be easy to modify (if needed).

These are two seemingly straightforward characteristics, but to achieve them, takes not only time and experience, but continuous learning too. A craftsman should be able to easily understand the request (even help with it) and pick the most appropriate tools for the job. A craftsman should be able to read code to business people so that they hear a narrative. A craftsman should be able to write such code.

I want my client to understand where their product stands at any time and what it can do. I do NOT want my client to be forced to listen to what kind of refactoring and how many unit tests I wrote yesterday. Or even worse, she shouldn’t be forced to decide if I need to write them tomorrow.

Also, I don’t want to hear that “we are thinkers, don’t force us to estimate“. Well, if you’re thinkers, then think about how long it takes you to do something you should already know how to do. Nobody said estimates are deadlines (did you?!!), but clients need to have a rough idea about resources they need to allocate. Your GPS doesn’t say “you know what… we’ll get there when we get there! The important thing is the magic I constantly use to display these awesome maps.

Trust

I don’t want to be cheesy and say that trust is earned, because of course it is. The difficult thing is to trust someone I don’t know. Looking at references and traces of past work helps, yes, but I also place a huge weight on the quality of the first discussion. It always served me well in assessing my future collaboration with anyone. Everyone is an intuitive psychologist by nature (not mentioning special health conditions).

The thing is, we almost always know (trained people are an exception, hence the “almost”) when someone is hiding important details in a conversation and we can also “feel” when what they say is what they think.

Obviously, the higher the level of trust, the higher the expected product quality. I’ll stop here and save the psychology magic for another time 🙂

Conclusion

These are my most valuable points of view, when looking at quality in software development. I also look for all of the above in business partners and also, I’m happy to say that for us, at bitGloss, quality is not just a buzzword.

Artificial intelligence

It’s been almost 20 years since I wrote my first neural network, during my university studies, in C#. Even back then, it was still unclear to me why this was called intelligence. I mean, after all, it was doing exactly what I programmed it to do and it felt like cheating to call “learning” what it was doing.


In the years since, I’ve read quite a few books on natural sciences (Darwin, Dawkins, Pinker…) and I discovered some wonderful things about the world we live in and how we “operate”. One extremely important thing I’ve learned is that while we know a lot about our brain and its functions, we still have a long way to go before we can say we know everything about it.


Natural intelligence has yet to define natural intelligence.


I still stand by my idea that software is just a tool that we use. It is not, in any way, an emerging new species that will overthrow humanity.
Is it smarter than us? No way! “Ok”, you might say, “but AlphaZero…”. AlphaZero what!? It can beat me at chess in no time? I bet it can’t if I spill a glass of water on top of it, can it? This is exactly the way we should evaluate things when we bring AI into the realm of inter-species competition. “Ok”, you might say again, “but what if all those specialized systems are put together into one big system that knows how to do every task? Isn’t that like a brain?”. No! Just read about cases where specialized brain modules were damaged, but somehow the functions were taken over by other modules. This is truly wonderful. Our brains adapt in such ways the aforementioned machine brain can’t.


A couple of words about the fear of AI destroying us: it’s most definitely possible for machines to destroy humanity (and possibly many other species as well), but that’s because WE control it. WE pull the trigger. WE have nukes, we don’t even need AI to do that. AI is just a means to an end in this scenario.

I believe that once we do fully solve the complex riddle of how the brain works, we might be able to recreate one from scratch. When that happens, it will probably be indistinguishable from a “natural” brain.

I will say this though: AI does have a bad PR, especially when it comes to public that is not trained in the subject at all (and most people don’t have a computer science degree). Of course people fear what they don’t understand AND is presented to them as a threat. Our brain always decides it’s better to be afraid and run if there is a sound in the tall grass of the savanna. The cost of being eaten by a predator hugely outweighs the cost of being wrong and tired from a run.


By the way, if you haven’t already done so, I strongly recommend you read Asimov’s “Robots” series. It’s wonderful! Now that’s a world I’d really like to live in.