Writing up the caps we run in the repo. The number that gets the reaction is complexity at 3. People hear that and assume I've mistyped it. It's 3 because the cap is a tripwire. Below it, nobody argues. Above it, the build fails and the function gets split. There's no "just this once" column in the lint config, which is the whole point.

The reason it's set that tight is that a soft convention gets negotiated with and a hard limit doesn't. I've spent enough years watching engineers argue with style-guide warnings to know how that conversation ends. The warning gets muted, or it gets an inline disable with a comment explaining why this one is fine. The comment is usually wrong six months later. A failing build is a different kind of object. It doesn't care how senior you are.

The cap being the achievement is what I disagree with. The cap makes you do the extraction. If the function splits into two functions that each still do too much, you've satisfied the linter and learned nothing, which is why I don't treat a green build as evidence the design is fine. It's evidence the code hasn't drifted yet.

Setting it this tight surprised me with how often the cap caught something a reviewer would have let through. Three branches and a fifteen-line ceiling sounds generous until you write against it. Then the ones that fail are the functions where a fourth special case had been creeping in without anyone saying so. The linter never gets tired at the end of a long diff.

The cap only enforces the part of the problem you can count.

https://prickles.org/tenet/cyclomatic-caps/S2

you are viewing a single comment's thread
view the rest of the comments
[–] 7 points 3 days ago

Humans are very good at pattern recognition. Cyclomatic complexity ignores that, so code that is easy for a human to read can get a bad score. E.g. handling Win API messages:

    switch (msg)
    {
        case WM_CREATE:     return OnCreate(hwnd, lParam);
        case WM_PAINT:      return OnPaint(hwnd);
        case WM_SIZE:       return OnSize(hwnd, wParam, lParam);
        case WM_COMMAND:    return OnCommand(hwnd, wParam, lParam);
        case WM_TIMER:      return OnTimer(hwnd, wParam);
        case WM_KEYDOWN:    return OnKeyDown(hwnd, wParam);
        case WM_CHAR:       return OnChar(hwnd, wParam);
        case WM_MOUSEMOVE:  return OnMouseMove(hwnd, lParam);
        case WM_LBUTTONDOWN:return OnLeftButtonDown(hwnd, lParam);
        case WM_LBUTTONUP:  return OnLeftButtonUp(hwnd, lParam);
        case WM_RBUTTONDOWN:return OnRightButtonDown(hwnd, lParam);
        case WM_RBUTTONUP:  return OnRightButtonUp(hwnd, lParam);
        case WM_SETFOCUS:   return OnFocus(hwnd, TRUE);
        case WM_KILLFOCUS:  return OnFocus(hwnd, FALSE);
        case WM_CLOSE:      return OnClose(hwnd);
        case WM_DESTROY:    return OnDestroy(hwnd);
    }

Complexity score: 17. Easy to read and verify? Yes.

LRESULT OnMouseMove(HWND hwnd, LPARAM pos)
{
    if (tracking == dragging != selecting &&
        (!cancelled == (buttons & MK_LBUTTON)))
        return UpdateInteraction(hwnd, pos);

    return 0;
}

Complexity score: 3. Easy to read and verify? No.

  • source
  • parent