• 0 Posts
  • 186 Comments
Joined 9 months ago
cake
Cake day: March 23rd, 2025

help-circle



  • A year or so before I started my current job, the team working on the project got split. Someone then decided that both teams should use different jira prefixes for tickets processed by each team. So they took all issues and automatically split them into two prefixes based on the people who implemented the ticket and renumbered everything. But they didn’t do the same in Gitlab merge requests, and they didn’t do it in git commit messages either.

    So now git and gitlab reference all old tickets by their old numbering system, but there’s no trace of these old numbers in Jira. It’s close to impossible to find the Jira ticket mentioned in a git commit message.

    Oh, and of course, nobody ever managed to properly link Jira and Gitlab (so that jira tickets contain the gitlab MRs, branches and commits) because for that you need a free Jira plugin and procurement wants a multi-page long description why this is needed, and it needs to be signed off by 5 people including the department lead and has to go through the whole procurement process before we can install that plugin.


  • You are obviously right about the things you are saying. I was specifically talking about code documentation on a class/method level. User documentation, architecture documentation or other high-level documentation doesn’t make sense in the code, of course.

    I have seen similar levels of documentation as you talk about (every line, every call documentated), but in flow charts in Confluence. That has the same issues as documenting every line of code in comments but worse.

    Just because a tool has some issues and limitations doesn’t mean it gets banned from our toolbox.

    This is very much it. Every tool can be abused and no tool is perfect. Code can have bugs and can be bad (and often both things happen). Should we now ban writing code?

    If the comment and the code doesn’t match with each other, which one is true?

    This can be true even with code alone. A while ago I found a bug in an old piece of code written by someone who left the company years ago.

    The method causing the bug was named something like isNotX(). In the function it returned isX. About half the places where the function was called, the returned value was assigned to a variable named isX and in the other half of the places the variable was named isNotX. So which is true?

    A javadoc-style comment could have acted as parity. Since comments are simpler to write than code, it’s easier to correctly explain the purpose of a function in there than in code.

    While in the example I referenced it was quite clear that something was wrong, this might not always be the case. Often the code looks consistent while actually being wrong. A comment can help to discern what’s going on there.

    Another example of that that we had at the same project:

    In the project there were bookings and prebookings. We had a customer-facing REST endpoint called “getSomeSpecialBookings” (it wasn’t called that, but the important thing was that this function would return a special subset of bookings). Other “get…Bookings” endpoints would return only return bookings and not prebookings, but this special endpoint would return both bookings and prebookings. A customer complained about that, so we fixed the “bug” and now this endpoint only returned bookings.

    (There was no comment anywhere and we couldn’t find anything relevant in Confluence.)

    Directly after the release some other customer creates a highest priority escalation because this change broke their workflow.

    Turns out, that endpoint only existed because that customer asked for it and the dev who implemented that endpoint just implemented it as the customer requested without documenting it anywhere.

    A comment would have been enough to explain that what this endpoint was doing was on purpose.

    We all know that code tends to be bad, especially after the project has been running for a few years and has been through a few hands.

    Why would anyone think that code is good enough to be the documentation?

    Luckily these days we have good tools in regards to source control, with things like feature branches, pull requests with tools that allow for discussion and annotation. That way at least usually the origin of a change is traceable.

    Sadly, we also have non-technical people running procurement and thus we keep switching tools because one is maginally cheaper or because cloud is cool right now (or not cool anymore right now) and migrations suck and then we end up with lost history.


  • I’m totally with Ousterhout here! Thanks for posting this great discussion!

    The problem with the “Clean code” approach of overdecomposition is that it doesn’t abstract the code away in meaningful ways. The code is still there and to debug/avoid bugs you still need to know all of it, if the methods are entangeld. So I still need to keep 500 lines of code in mind, but now they aren’t all in one file where I can easily follow them, but instead spread over 40 files, each just containing 1-2 line methods.

    I’m also very much against “Clean code”'s recommendations on comments. In the end it either leads to no documentation or documentation lost somewhere in confluence that nobody ever reads or updates because it’s not where it’s needed.

    Getting developers to read and update documentation is not an easy task, so the easier it is to find and update the documentation the more likely it is that the documentation is actually used. And there is no easier-to-access place for documentation than in comments right in the code. I really like Javadoc-style documentation since it easily explains the interface right where it’s needed and neatly integrates with IDEs.





  • Don’t know if there’s a ready-made site for stuff like that, but it’s not hard to do.

    Here’s a quick and dirty AI generated piece of trash code as a proof of concept:

    # sankey_hiring_funnel_direct.py
    # Requires: plotly
    # Install: pip install plotly
    
    import plotly.graph_objects as go
    
    # Node labels (unique)
    labels = [
        "Network",            # 0
        "Hiring.cafe",        # 1
        "Abandoned Lead",     # 2
        "Applied",            # 3
        "Rejected",           # 4
        "No Response",        # 5
        "Screener",           # 6
        "Rejected by Screen", # 7
        "Full Round",         # 8
        "Rejected by Panel",  # 9
        "Offer",              #10
        "Accepted",           #11
        "Declined"            #12
    ]
    
    # Colors for the two source groups (consistent)
    network_color = "rgba(31,119,180,0.8)"      # blue-ish
    hiring_color  = "rgba(255,127,14,0.8)"      # orange-ish
    
    sources = []
    targets = []
    values  = []
    link_colors = []
    
    def add_link(src_idx, tgt_idx, val, color):
        sources.append(src_idx)
        targets.append(tgt_idx)
        values.append(val)
        link_colors.append(color)
    
    # Direct flows from Network and Hiring.cafe into Abandoned Lead and Applied
    add_link(0, 2, 1, network_color)    # Network -> Abandoned Lead (1)
    add_link(1, 2, 58, hiring_color)    # Hiring.cafe -> Abandoned Lead (58)
    add_link(0, 3, 11, network_color)   # Network -> Applied (11)
    add_link(1, 3, 70, hiring_color)    # Hiring.cafe -> Applied (70)
    
    # Applied -> Rejected, No Response, Screener (split by original group)
    add_link(3, 4, 5, network_color)    # Applied -> Rejected (network 5)
    add_link(3, 4, 40, hiring_color)    # Applied -> Rejected (hiring 40)
    add_link(3, 5, 3, network_color)    # Applied -> No Response (network 3)
    add_link(3, 5, 15, hiring_color)    # Applied -> No Response (hiring 15)
    add_link(3, 6, 4, network_color)    # Applied -> Screener (network 4)
    add_link(3, 6, 15, hiring_color)    # Applied -> Screener (hiring 15)
    
    # Screener -> Rejected by Screen, Full Round
    add_link(6, 7, 1, network_color)    # Screener -> Rejected by Screen (network 1)
    add_link(6, 7, 5, hiring_color)     # Screener -> Rejected by Screen (hiring 5)
    add_link(6, 8, 3, network_color)    # Screener -> Full Round (network 3)
    add_link(6, 8, 10, hiring_color)    # Screener -> Full Round (hiring 10)
    
    # Full Round -> Rejected by Panel, Offer
    add_link(8, 9, 1, network_color)    # Full Round -> Rejected by Panel (network 1)
    add_link(8, 9, 7, hiring_color)     # Full Round -> Rejected by Panel (hiring 7)
    add_link(8, 10, 2, network_color)   # Full Round -> Offer (network 2)
    add_link(8, 10, 3, hiring_color)    # Full Round -> Offer (hiring 3)
    
    # Offer -> Accepted, Declined
    add_link(10, 11, 1, network_color)  # Offer -> Accepted (network 1)
    add_link(10, 12, 1, network_color)  # Offer -> Declined (network 1)
    add_link(10, 12, 3, hiring_color)   # Offer -> Declined (hiring 3)
    
    # Sanity check
    assert len(sources) == len(targets) == len(values) == len(link_colors)
    
    # Node colors (visual guidance)
    node_colors = [
        "rgba(31,119,180,0.9)",   # Network
        "rgba(255,127,14,0.9)",   # Hiring.cafe
        "rgba(220,220,220,0.9)",  # Abandoned Lead
        "rgba(200,200,200,0.9)",  # Applied
        "rgba(220,180,180,0.9)",  # Rejected
        "rgba(200,200,220,0.9)",  # No Response
        "rgba(200,220,200,0.9)",  # Screener
        "rgba(255,200,200,0.9)",  # Rejected by Screen
        "rgba(210,210,255,0.9)",  # Full Round
        "rgba(240,200,220,0.9)",  # Rejected by Panel
        "rgba(200,255,200,0.9)",  # Offer
        "rgba(140,255,140,0.9)",  # Accepted
        "rgba(255,140,140,0.9)"   # Declined
    ]
    
    fig = go.Figure(data=[go.Sankey(
        node=dict(
            pad=18,
            thickness=18,
            line=dict(color="black", width=0.5),
            label=labels,
            color=node_colors
        ),
        link=dict(
            source=sources,
            target=targets,
            value=values,
            color=link_colors,
            hovertemplate='%{source.label} → %{target.label}: %{value}<extra></extra>'
        )
    )])
    
    fig.update_layout(
        title_text="Hiring funnel Sankey — direct source flows (no Leads node)",
        font_size=12,
        height=700,
        margin=dict(l=20, r=20, t=60, b=20)
    )
    
    fig.show()
    
    # To save as interactive HTML:
    # fig.write_html("sankey_hiring_funnel_direct.html", include_plotlyjs='cdn')
    

    Couldn’t be bothered to write this by hand for just an online comment. There’s enough that can be improved with this, but I think it’s ok to show how it can be done quite easily.


  • The better option is to keep colors from the original input stream for the flows instead of making the flows an uniform color.

    In the input on the left you have pink, green and blue.

    Keep these colors throughout the graph.

    Except of the input, all of the other stages only ever split up and never merge, so keeping this single set of colors is enough.

    The other option would be to get rid of the “leads” stage, since it actually doesn’t change any state. All the other stages are an action that happens (e.g. “Applied” changes the state of the application from being just a lead to being an open application and it also filters out data for being e.g. abandoned). But the “leads” stage means the same thing as the first stage. So drop the “leads” stage and instead make flows go from all three input stages directly into “bad lead”, “abandoned” or “applied”.

    Combine both to get the best result.


  • A lot of the systems are quite stabilized. No need for a new OS, a new browser, a new language.

    Even if the old stuff isn’t perfectly optimal, having to setup a fully-new ecosystem is so incredibly costly that it’s just not worth it.

    That’s why you see new developments (e.g. Typescript or Kotlin) piggyback on older ecosystems (e.g. JavaScript or Java compatibility).

    Typescript could have been better if it was a completely fresh development without being encumbered by the madness that is JavaScript. But without JavaScript compatibility and thus acces to the JS ecosystem, nobody would have switched to TS.

    All these systems heavily benefit from network effects, which makes it hard to impossible for completely new systems to emerge.

    This is doubly strong for consumer-facing software. Linux only became a viable mainstream option due to Wine/Proton/… allowing users to easily run Windows programs. Without Windows compatibility, Linux would still be at <1% desktop market share.

    It’s also the same reason why everyone’s making chromium-based browsers: Because that way they all work the same.

    Disruptive change happens when you get a completely fresh use case. Microsoft completely destroyed the likes of Commodore and IBM when home computers became something that everyone had in their homes.

    Smartphones becoming mainstream allowed Google and Apple, who were both completely new to the mobile OS business, to win against established mobile OS companies, because nothing was entrenched in the late 2000s mobile OS landscape.

    OpenAI, Anthropic, Midjourney and so on are wiping the floor with established software powerhouses in the AI space.

    But after the disruption follows stabilization. A product that has reached market saturation will only be replaced by incremental, compatible improvements.




  • There are a lot of top-to-bottom languages in Asia. Some chinese languages for example are traditionally written top to bottom.

    Bidirectional text only really occurs when mixing languages, like in the example above where RTL Hebrew is mixed with LTR English (or in this case specifically LTR file paths that have originally been created in the context of an LTR language and thus are LTR).

    If there was actual TTB language support in Windows Explorer, and you had a file path incorporating both TTB file names and LTR file endings and drive letters, then you’d also have the same issue with mixing LTR and RTL, only that you are now mixing writing directions in two dimensions.

    But I’m guessing even though Unicode’s stated goal is to encode all writing, TTB is probably where they drew the line.




  • I worked in a startup that was expanding to the US. For that purpose we hired two sales people with really good connections to the largest potential customer company in the US.

    We had this product on our price list. It was a small and relatively inexpensive product (in the order of a few €100, while most of our other products had four to six figure prices). This product was stuck in development hell. We had a half-functioning prototype with the wrong chip in there, and it would need a full rework. It was a terrible product and far from usable.

    So that new sales guy calls up my boss, the CEO, who’s a notorious lier and proud of that fact and asks him if that device is ready to be sold. The sales guy says they don’t have any customers asking for this and it’s totally ok if it’s not ready to be sold, he just wants to know whether it’s ready.

    With no need and no pressure at all, the CEO says “Oh, it’s completely finished. You can sell that with no issues at all.”

    The sales guy believes it, and tries to sell this to that one biggest potential customer. The customer likes the idea and asks for a demo. Of course, we cannot provide one.

    That was it. That customer blacklisted us and never bought anything at all from us. It burned the two sales people, they never managed to get any of our products sold in worthwhile quantities and a year later we shut down the US division.