Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Clean Code Revisited in Age of AI (Droidcon USA)

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Clean Code Revisited in Age of AI (Droidcon USA)

For a generation of developers, Uncle Bob’s Clean Code wasn’t just a book—it was a manifesto. While "Effective [Add Language here]" books guide us in syntax, Clean Code taught us our coding standards. A set of rules so strict that many criticized it, such as the number of lines per method. But the game has changed. The hands on the keyboard are often no longer human, and the code our LLMs write is far from what traditional “Clean Code” is. AI favors verbosity over abstraction and completion over long-term maintainability; it even adds “evil” comments everywhere because that’s how its completion works. Are these rules still valid when refactorings happen in the blink of an eye? But humans will still review the code, won't they? Let’s revisit the rules!

Avatar for Danny Preussler

Danny Preussler

July 16, 2026

More Decks by Danny Preussler

Other Decks in Programming

Transcript

  1. • Early 90s Commodore 64 • Started programming with Pascal

    • Most of the career on client side: embedded systems, J2ME, Blackberry, Android apps • Seven years at SoundCloud My journey
  2. One might argue that a book about code is somehow

    behind the times - that code is no longer the issue; that we should be concerned about models and requirements instead. Indeed some have suggested that we are close to the end of code. That soon all code will be generated instead of written. That programmers simply won’t be needed because business people will generate programs from specifications
  3. One might argue that a book about code is somehow

    behind the times - that code is no longer the issue; that we should be concerned about models and requirements instead. Indeed some have suggested that we are close to the end of code. That soon all code will be generated instead of written. That programmers simply won’t be needed because business people will generate programs from specifications
  4. One might argue that a book about code is somehow

    behind the times - that code is no longer the issue; that we should be concerned about models and requirements instead. Indeed some have suggested that we are close to the end of code. That soon all code will be generated instead of written. That programmers simply won’t be needed because business people will generate programs from specifications Chapter 1, Clean Code, 2009
  5. It was never about “writing” code: “The ratio of time

    spent reading versus writing is well over 10 to 1 [..] Because this ratio is so high, we want the reading of code to be easy, even if it makes the writing harder.” Clean Code, 2009
  6. It was never about “writing” code: “The ratio of time

    spent reading versus writing is well over 10 to 1 [..] Because this ratio is so high, we want the reading of code to be easy, even if it makes the writing harder.” Clean Code, 2009
  7. The ideal number of arguments for a function is zero.

    Next comes one, Followed closely by two. Three arguments should be avoided where possible. More than three requires very special justification and then shouldn’t be used anyway.
  8. The ideal number of arguments for a function is zero.

    Next comes one, Followed closely by two. Three arguments should be avoided where possible. More than three requires very special justification and then shouldn’t be used anyway.
  9. The ideal number of arguments for a function is zero.

    Next comes one, Followed closely by two. Three arguments should be avoided where possible. More than three requires very special justification and then shouldn’t be used anyway.
  10. The ideal number of arguments for a function is zero.

    Next comes one, Followed closely by two. Three arguments should be avoided where possible. More than three requires very special justification and then shouldn’t be used anyway.
  11. Some languages allow you to name the arguments [..] I

    don't worry as much about the order of the arguments and might, on occasion,pass more than three. (Clean Code 2nd Edition)
  12. The first rule of functions is that they should be

    small. The second rule of functions is that they should be smaller than that.
  13. An LLM does’nt read word-by-word like a human it converts

    your code into numbers (tokens) then calculates how every single token relates to every other token. The mathematical grid is the Attention Matrix
  14. An LLM does’nt read word-by-word like a human it converts

    your code into numbers (tokens) then calculates how every single token relates to every other token. The mathematical grid is the Attention Matrix
  15. An LLM does’nt read word-by-word like a human it converts

    your code into numbers (tokens) then calculates how every single token relates to every other token. The mathematical grid is the Attention Matrix
  16. Proximity is power! [Line 45: val userId = 10] ───►

    [Line 46: fetchProfile(userId)] ▲ Strongest Attention Weight
  17. Distance is noise! [Line 5: val userId = 10] │

    ▼ Huge Distance │ [Line 250: fetchProfile(userId)] ▲ ░░░░ Faded Attention Weight
  18. Distance is noise! [Line 5: val userId = 10] │

    ▼ Huge Distance │ [Line 250: fetchProfile(userId)] ▲ ░░░░ Faded Attention Weight (why does var exist and what are constraints? ¯\_(ツ)_/¯)
  19. Base Attention x 0.99 Distance = Signal Strength // Distance

    = 1 token => 0.99. Perfect Recall // Distance = 500 => 0.006 The AI is purely guessing Multiplicative Decay Risk!
  20. Base Attention x 0.99 Distance = Signal Strength // Distance

    = 1 token => 0.99. Perfect Recall // Distance = 500 => 0.006 The AI is purely guessing Multiplicative Decay Risk! The deeper the call tree => exponentially higher risk of hallucination
  21. In information theory entropy is a measure of unpredictability For

    a good Next-Token Prediction, entropy needs to be low
  22. In information theory entropy is a measure of unpredictability For

    a good Next-Token Prediction, entropy needs to be low
  23. When a function does only one thing, (token probability very

    constraint => Low Entropy. When a function does multiple things, it flattens the probability curve => High Entropy
  24. When a function does only one thing, (token probability very

    constraint => Low Entropy. When a function does multiple things, it flattens the probability curve => High Entropy
  25. LLMs are exceptionally good at writing unit tests for single-responsibility

    functions Because transformers are optimized for mapping a clear set of inputs to a predictable output.
  26. LLMs are exceptionally good at writing unit tests for single-responsibility

    functions Because transformers are optimized for mapping a clear set of inputs to a predictable output.
  27. LLMs are exceptionally good at writing unit tests for single-responsibility

    functions Because transformers are optimized for mapping a clear set of inputs to a predictable output. "one responsibility" -> “very predictable”
  28. Usually comments are crutches or excuses for poor code or

    justifications for insufficient decisions, amounting to little more than the programmer talking to himself
  29. LLMs cannot "think ahead" They write a comment to force

    themself to compute the logic. It describes what needs to come next
  30. LLMs cannot "think ahead" They write a comment to force

    themself to compute the logic. It describes what needs to come next Comments = "Chain of Thought" Scaffolding
  31. Rather than spend your time writing the comments that explain

    the mess you’ve made, spend it cleaning that mess.
  32. We can model code generation as a sequence of states

    the next token depends heavily on the immediately preceding tokens. LLM have no independent standard of "good" A low quality code still has as a heavy mathematical weight
  33. We can model code generation as a sequence of states

    the next token depends heavily on the immediately preceding tokens. LLM have no independent standard of "good" A low quality code still has as a heavy mathematical weight
  34. When keywords like "hack", "temporary", and poorly structured syntax are

    loaded into the active context the model mathematically assumes that the expected distribution is low-quality Once the matrix shifts, the AI gets mathematically locked into a loop of generating low-quality output
  35. When keywords like "hack", "temporary", and poorly structured syntax are

    loaded into the active context the model mathematically assumes that the expected distribution is low-quality Once the matrix shifts, the AI gets mathematically locked into a loop of generating low-quality output
  36. When keywords like "hack", "temporary", and poorly structured syntax are

    loaded into the active context the model mathematically assumes that the expected distribution is low-quality Once the matrix shifts, the AI gets mathematically locked into a loop of generating low-quality output
  37. Few practices are as odious as commenting-out code [..] There

    was a time, back in the sixties, when commenting-out code might have been useful. But we’ve had good source code control systems for a very long time now
  38. An LLM does not have a binary skip-flag func calculateTotal()

    ──► Embedded as dense vectors // func oldBadCalculate() ──► Embedded as nearly identical vectors
  39. The AI reasons (purely through statistical probability) that since this

    code pattern exists, it must represent your architectural standard
  40. We would like a source file to be like a

    newspaper article. The topmost parts of the source file should provide the high-level Detail should increase as we move downward, until at the end we find the lowest level functions
  41. fun processUploadedTrack(trackId: UUID, rawBytes: ByteArray): ProcessingResult { val audioData =

    prepareAudioPayload(trackId, rawBytes) return runPipelineVerification(audioData) } fun prepareAudioPayload(trackId: UUID, rawBytes: ByteArray): AudioPayload { val format = detectAudioFormat(rawBytes) return AudioPayload(id = trackId, data = rawBytes, format = format) } fun runPipelineVerification(payload: AudioPayload): ProcessingResult { if (!isValidBitrate(payload.data, payload.format)) { return ProcessingResult.Rejected("Unsupported audio bitrate format.") } ..
  42. In long context windows, models can suffer from a phenomenon

    where the LLM pays high attention to the very beginning and very end of its input prompt, its accuracy degrades for tokens in the middle The "Lost in the Middle" effect
  43. In long context windows, models can suffer from a phenomenon

    where the LLM pays high attention to the very beginning and very end of its input prompt, its accuracy degrades for tokens in the middle The "Lost in the Middle" effect
  44. In long context windows, models can suffer from a phenomenon

    where the LLM pays high attention to the very beginning and very end of its input prompt, its accuracy degrades for tokens in the middle The "Lost in the Middle" effect
  45. In long context windows, models can suffer from a phenomenon

    where the LLM pays high attention to the very beginning and very end of its input prompt, its accuracy degrades for tokens in the middle The "Lost in the Middle" effect
  46. In an ideal world, an attention matrix would look like

    a perfectly balanced grid. In reality, the attention weights form a sharp U-shaped curve. The Middle is a mathematical dead zone: As the token distance from both boundaries increases, the cross-attention weights flatten out
  47. In an ideal world, an attention matrix would look like

    a perfectly balanced grid. In reality, the attention weights form a sharp U-shaped curve. The Middle is a mathematical dead zone: As the token distance from both boundaries increases, the cross-attention weights flatten out
  48. In an ideal world, an attention matrix would look like

    a perfectly balanced grid. In reality, the attention weights form a sharp U-shaped curve. The Middle is a mathematical dead zone: As the token distance from both boundaries increases, the cross-attention weights flatten out
  49. fun processUploadedTrack(trackId: UUID, rawBytes: ByteArray): ProcessingResult { val audioData =

    prepareAudioPayload(trackId, rawBytes) return runPipelineVerification(audioData) } fun prepareAudioPayload(trackId: UUID, rawBytes: ByteArray): AudioPayload { val format = detectAudioFormat(rawBytes) return AudioPayload(id = trackId, data = rawBytes, format = format) } fun runPipelineVerification(payload: AudioPayload): ProcessingResult { if (!isValidBitrate(payload.data, payload.format)) { return ProcessingResult.Rejected("Unsupported audio bitrate format.") } ..
  50. Either your function should change the state of an object,

    or it should return some information about that object. Doing both often leads to confusion
  51. fun getAndRemoveTrack(urn): Urn ┌───────────────────────────┐ │ W R O N G

    ✖ 🛑 │ └───────────────────────────┘
  52. fun seek(ms: Long): Long ┌────────────────────────────────┐ │ A L S O

    W R O N G ✖ 🛑 │ └────────────────────────────────┘
  53. Another LLM discourse While the Attention Matrix represents how tokens

    connect to each other The Vector Space (Embeddings) represents what tokens mean
  54. Words with similar meanings sit physically close to each other

    Query Words (get, calculate, fetch, view) map to vectors in a region of Immutability, Safety, and Observation Command Words (save, update, delete, process) map to a completely different region associated with Mutation, State Change, and Side Effects
  55. Words with similar meanings sit physically close to each other

    Query Words (get, calculate, fetch, view) map to vectors in a region of Immutability, Safety, and Observation Command Words (save, update, delete, process) map to a completely different region associated with Mutation, State Change, and Side Effects
  56. Words with similar meanings sit physically close to each other

    Query Words (get, calculate, fetch, view) map to vectors in a region of Immutability, Safety, and Observation Command Words (save, update, delete, process) map to a completely different region associated with Mutation, State Change, and Side Effects
  57. Command and Query violation will force a Semantic Contradiction inside

    the attention weights. The model get trapped in a mathematical contradiction Entropy spikes Predictable Hallucinations \_(ツ)_/¯
  58. Command and Query violation will force a Semantic Contradiction inside

    the attention weights. The model get trapped in a mathematical contradiction Entropy spikes Predictable Hallucinations \_(ツ)_/¯
  59. Command and Query violation will force a Semantic Contradiction inside

    the attention weights. The model get trapped in a mathematical contradiction Entropy spikes Predictable Hallucinations \_(ツ)_/¯
  60. Command and Query violation will force a Semantic Contradiction inside

    the attention weights. The model get trapped in a mathematical contradiction Entropy spikes Predictable Hallucinations \_(ツ)_/¯
  61. Duplication is the primary enemy of a well-designed system. It

    represents additional work, additional risk, and additional unnecessary complexity
  62. executor.process(payload) Because the function name is generic, the probability distribution

    flattens out entirely. No single token has a dominant probability weight, the entropy is maximized. To avoid DRY: We start abstracting:
  63. "Healthy Duplication" With AI: duplication is judged by semantic dependency

    “if merging them forces the AI to leap across files or lose local context, keep them separate”
  64. This kind of code is often called a train wreck

    because it look like a bunch of coupled train cars. Chains of calls like this are generally considered to be sloppy style and should be avoided
  65. val zipCode = order.getCustomer().getProfile().getAddress().getZipCode() Loads 4 classes into the context

    window signal-to-noise ratio drops Every jump decreases probability
  66. Naming Use intention-revealing names 🟢 Avoid disinformation 🟢 Make meaningful

    distinctions 🟢 Use pronounceable names 🔴 Use searchable names 🟢 Avoid encodings / Hungarian / m_ 🔴 Avoid mental mapping 🔴 Class names should be nouns 🟢 Method names should be verbs 🟢 Pick one word per concept 🟢 Don't use same word for diff concepts 🟢 Use solution/problem-domain names 🟢 Add meaningful context 🟢 Don't add gratuitous context 🔴
  67. Functions Functions should be small / smaller 🔴 Functions should

    do one thing 🟢 One level of abstraction per function 🟢 Read code top-down 🟢 Use descriptive names 🟢 Prefer fewer arguments / No args 🟢 Avoid flag (boolean) arguments 🟢 Avoid output arguments 🟢 Separate commands from queries 🟢 Prefer exceptions over error codes 🟢 Eliminate duplication 🔴
  68. Comments Comments are failures 🔴 Explain yourself in code 🟢

    Good comments are rare 🔴 Don't use comments for poor code 🟢 Keep comments accurate 🟢 Avoid redundant comments 🟢 Avoid misleading comments 🟢 Avoid mandated comments 🟢 Avoid journal comments 🟢 Avoid commented-out code 🟢 Legal comments 🟢 Warning comments 🟢 TODO comments 🟢 Amplification comments 🟢 Public API documentation 🟢
  69. Formatting Vertical openness between concepts 🔴 Related concepts vertically close

    🟢 Declare variables near usage 🟢 Keep dependent functions close 🟢 Conceptual affinity implies proximity 🟢 Lines should be reasonably short 🟢 Team follows one formatting style 🟢
  70. Objects/Data Hide implementation details 🟢 Prefer behavior-rich objects 🔴 Avoid

    hybrids of objects/structures 🟢 Use data structures for transport 🟢 Use objects for behavior 🟢 Follow the Law of Demeter 🟢 Avoid train wrecks 🟢
  71. Errors Use exceptions instead of error codes 🟢 Write try-catch-finally

    first 🔴 Provide context in exceptions 🟢 Define exception classes for callers 🟢 Don't return / pass null 🟢 Use special-case objects 🟢
  72. Boundaries Encapsulate third-party APIs 🟢 Learn third-party APIs with tests

    🟢 Isolate external dependencies 🟢 Keep boundaries clean 🟢
  73. Tests Keep tests clean 🟢 One assert per test (or

    close) 🔴 Tests should be readable 🟢 Tests should be fast / F.I.R.S.T. 🟢 Tests should be independent 🟢 Tests should be repeatable 🟢 Tests should be self-validating 🟢 Tests should be timely 🔴
  74. Classes Classes should be small / smaller 🔴 Single Responsibility

    Principle 🟢 High cohesion 🟢 Few instance variables 🟢 Organize for change 🟢 Depend on abstractions 🟢
  75. Systems Separate construction from use 🟢 Separate policy from detail

    🟢 Use dependency injection 🟢 Build systems incrementally 🟢 Preserve architectural boundaries 🟢
  76. Emergence 1. Runs all tests 🟢 2. Contains no duplication

    🔴 3. Expresses intent 🟢 4. Minimizes classes and methods 🔴
  77. The Single Responsibility Principle (SRP) states that a class or

    module should have one, and only one, reason to change.
  78. class TrackProcessor { // Audio Engineering Team's territory fun processAudio(track:

    Track) { //... } // Finance Team's territory fun calculateRoyaltyPayout(track: Track): Double { //... } }
  79. class AudioTrackProcessor { fun processAudio(trackId: UUID) { // ...} }

    class RoyaltyCalculator { fun calculateRoyaltyPayout(track: Track): Double { // ...} }
  80. “A file should contain all the necessary context to execute

    a prompt” Keep Token Distance to near zero
  81. “A file should contain all the necessary context to execute

    a prompt” Keep Token Distance to near zero
  82. Keep together if: They change together for the exact same

    reason They share the same boundary, lifecycle, vocabulary Goal: clean collection of standalone, readable stories
  83. class UploadTrackAction(val storageGateway: TrackStorageGateway) { data class Request( val artistId:

    UUID, val title: String, val audioBytes: ByteArray ) fun execute(request: Request): Response { // .. } private fun validatePayload(request: Request) { // .. } interface TrackStorageGateway { // .. } }
  84. "The behavior of a unit of code should be as

    obvious as possible by looking only at that unit of code." Carson Gross (HTMX)
  85. File size? We know from .md files: > 300 lines

    LLM compliance drops > 1500 lines Multiplicative Decay if needed: Move from “Context File” to a “Context Directory”
  86. File size? We know from .md files: > 300 lines

    LLM compliance drops > 1500 lines Multiplicative Decay if needed: Move from “Context File” to a “Context Directory”
  87. This is not new! Do not slice by technical layers

    (Controllers, Services, Repositories) Slice by business feature!
  88. “The shift now is from programs and apps to systems.”

    (Grady Booch in The Pragmatic Engineer)
  89. The Grady Booch in 2026 “The hard parts remain human:

    - What problem matters? - Which tradeoffs are acceptable? - What do we optimize for? - What are the consequences when this system fails?”
  90. Now you've got the horsepower. You tell that AI to

    cover the damned code. And then you use a mutation tester [..] will make changes in your source code and run all the tests. And if the tests don't fail, then it'll write a test to make it fail, which means that you will have test coverage by God, you will have test coverage. Uncle Bob
  91. Now you've got the horsepower. You tell that AI to

    cover the damned code. And then you use a mutation tester [..] will make changes in your source code and run all the tests. And if the tests don't fail, then it'll write a test to make it fail, which means that you will have test coverage by God, you will have test coverage. Uncle Bob
  92. Now you've got the horsepower. You tell that AI to

    cover the damned code. And then you use a mutation tester [..] will make changes in your source code and run all the tests. And if the tests don't fail, then it'll write a test to make it fail, which means that you will have test coverage by God, you will have test coverage. Uncle Bob
  93. And then you know what else you could do. You

    could analyze the code for quality. You could write a tool that looks at the cyclomatic complexity. In fact, there's a great tool for this, 20 years old. It's called CRAP. [..] And you can tell the AI drive the CRAP down to low, which will force it to cut up all the big functions into little tiny functions and to cover them all with tests by God. Uncle Bob
  94. And then you know what else you could do. You

    could analyze the code for quality. You could write a tool that looks at the cyclomatic complexity. In fact, there's a great tool for this, 20 years old. It's called CRAP. [..] And you can tell the AI drive the CRAP down to low, which will force it to cut up all the big functions into little tiny functions and to cover them all with tests by God. Uncle Bob
  95. And then you know what else you could do. You

    could analyze the code for quality. You could write a tool that looks at the cyclomatic complexity. In fact, there's a great tool for this, 20 years old. It's called CRAP. [..] And you can tell the AI drive the CRAP down to low, which will force it to cut up all the big functions into little tiny functions and to cover them all with tests by God. Uncle Bob
  96. The CRAP Index Test coverage alone doesn't tell how risky

    a change can be Blend Cyclomatic Complexity and Code Coverage into an index: CRAP
  97. The CRAP Index If a piece of code has low

    complexity: Test coverage can be lower If a piece of code has massive complexity, it is highly prone to breaking: Needs high test coverage
  98. Keep Context Pure - Fights Attention Pollution - Avoid gravitating

    towards bad comments, useless, hacky or dead code
  99. Optimize for Locality - Fight Multiplicative Decay - Kee declarations,

    usage, and helpers physically tight - Maximizes Signal to Noise
  100. The Single Context Principle - Keep semantic context together -

    The ultimate Entropy weapon - Reduces hallucinations drastically
  101. - Make code readable for AI - Makes you aware

    when and why AI fails - Force the AI to write clean code - Move attention to architecture - Use freed time to work on tests Why Clean Code Today?
  102. The end 🦋 preussler.berlin 🦋 🦋 soundcloud.dev 🦋 🧡 developers.soundcloud.com

    🧡 We are hiring! Android Developer (NYC or East Coast)
  103. LLM have no independent standard of "good" The existing style,

    quality, and even your deprecating comments exert a massive gravitational pull. Bad code with explanation becomes self fulfilling prophecy