bookokrat 0.2.3

A terminal-based EPUB Books reader
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
# Exported Comments

## Chapter 1. Clean Code

#### Productivity
*Note // 11-09-25 10:51*
> todo: make a comparison how much padding and ranting was added to this chapter in comparison to first edition. and how empty it is

I’ve asked you before if you have ever been significantly impeded by bad code. If you are a programmer of any number of years, you most certainly have. Once again, my question is: Why did you write it?
*Note // 11-09-25 10:51*
> todo: make a comparison of how much padding were added to the second edition. and rants and empty references. and how it doesn't add much

Now look, I know all the excuses. They all boil down to: They never give us time. This is false. The only way to go fast is to go well. The best way to meet the schedule, the best way to make the deadline, is to do a good job. If you want to go as fast as you can, don’t do the things that slow you down! Clean your code!
*Note // 11-09-25 10:50*
> this is false. quite often bad code happens because the author doesn't know how to make it better

It turns out that if you know enough calculus, you can prove that Achilles will, in fact, pass the tortoise. But that math doesn’t always work for software projects.
*Note // 11-09-25 10:52*
> like this is an example of useless analogy

There is no escape from this logic. You cannot write code if you cannot read the surrounding code. The code you are trying to write today will only be easy to write if the surrounding code is easy to read. So, if you want to go fast, if you want to get done quickly, if you want your code to be easy to write, make it easy to read.
*Note // 01-07-26 12:44*
> hey yo!

Leave the campground cleaner than you found it.
*Note // 11-09-25 10:55*
> can add same idea that this works on a small scale, but is a disaster on a large one

---

## Chapter 2. Clean That Code!

¹ By some definition of that word.
*Note // 11-09-25 10:57*
> this is annoyingly useless remarks. good illustration of how much style of the book has degraded

Here are the tests I used. I had my doubts about whether they were complete.
*Note // 11-09-25 10:59*
> good idea to introduce notion of fuzz or property based testing

```
package fromRoman;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class FromRoman {
  private String roman;
  private List<Integer> numbers = new ArrayList<>();
  private int charIx;
  private char nextChar;
  private Integer nextValue;
  private Integer value;
  private int nchars;
  Map<Character, Integer> values = Map.of(
    'I', 1,
    'V', 5,
    'X', 10,
    'L', 50,
    'C', 100,
    'D', 500,
    'M', 1000);

  public FromRoman(String roman) {
    this.roman = roman;
  }

  public static int convert(String roman) {
    return new FromRoman(roman).doConversion();
  }

  private int doConversion() {
    checkInitialSyntax();
    convertLettersToNumbers();
    checkNumbersInDecreasingOrder();
    return numbers.stream().reduce(0, Integer::sum);
  }

  private void checkInitialSyntax() {
    checkForIllegalPrefixCombinations();
    checkForImproperRepetitions();
  }

  private void checkForIllegalPrefixCombinations() {
    checkForIllegalPatterns(
      new String[]{"VIV", "IVI", "IXI", "IXV", "LXL", "XLX",
                   "XCX", "XCL", "DCD", "CDC", "CMC", "CMD"});
  }

  private void checkForImproperRepetitions() {
    checkForIllegalPatterns(
      new String[]{"IIII", "VV", "XXXX", "LL", "CCCC", "DD", "MMMM"});
  }

  private void checkForIllegalPatterns(String[] patterns) {
    for (String badString : patterns)
      if (roman.contains(badString))
        throw new InvalidRomanNumeralException(roman);
  }

  private void convertLettersToNumbers() {
    char[] chars = roman.toCharArray();
    nchars = chars.length;
    for (charIx = 0; charIx < nchars; charIx++) {
      nextChar = isLastChar() ? 0 : chars[charIx + 1];
      nextValue = values.get(nextChar);
      char thisChar = chars[charIx];
      value = values.get(thisChar);
      switch (thisChar) {
        case 'I' -> addValueConsideringPrefix('V', 'X');
        case 'X' -> addValueConsideringPrefix('L', 'C');
        case 'C' -> addValueConsideringPrefix('D', 'M');
        case 'V', 'L', 'D', 'M' -> numbers.add(value);
        default -> throw new InvalidRomanNumeralException(roman);
      }
    }
  }

  private boolean isLastChar() {
    return charIx + 1 == nchars;
  }

  private void addValueConsideringPrefix(char p1, char p2) {
    if (nextChar == p1 || nextChar == p2) {
      numbers.add(nextValue - value);
      charIx++;
    } else numbers.add(value);
  }

  private void checkNumbersInDecreasingOrder() {
    for (int i = 0; i < numbers.size() - 1; i++)
      if (numbers.get(i) < numbers.get(i + 1))
        throw new InvalidRomanNumeralException(roman);
  }

  public static
  class InvalidRomanNumeralException extends RuntimeException {
    public InvalidRomanNumeralException(String roman) {
      super("Invalid Roman numeral: " + roman);
    }
  }
}
```

*Line 1 // 11-09-25 22:21*
> this whole code snippet is a good example of why "clean code"(tm) style sucks even in the second edition. 
> 
> i would highlight 3 pillars of the style:
> 
> 1) avoid comments. over-use "description as a name" instead
> 2) extract a lot of tiny functions that being used only once 
> 3) move as much of method/functions parameters to the class state and pass them implicitely from everywhere to everwhere
> 
> Despite all the caveats thats being added in the second edition, the Martin's style stayed pretty much the same. 
> And it is very recognizable style
> (todo: add meme about overwhorked muscle)

*Lines 8-14 // 11-09-25 11:56*
> this is unfortunate mix of input data, intermediate computation state, and output data

*Line 15 // 11-09-25 11:46*
> this should be "static private final"

*Line 21 // 12-13-25 21:37*
> PUK!

*Line 24 // 11-09-25 11:56*
> this should be private, if we want convert to be an entry point

*Lines 39-53 // 11-09-25 12:01*
> there is no need to split validation in 3 methods. this just pollutes name space. 
> invalid patterns can be extracted into a constant to avoid useless allocations

Future Bob: Two months later I’m torn. The first version, ugly as it was, was not as chopped up as this one. It’s true that the names and the ordering of the extracted functions read like a story and are a big help in understanding the intent; but there were several times that I had to scroll back up to the top to assure myself about the types of instance variables. I found the choppiness, and the scrolling, to be annoying. However, and this is critical, I am reading this cleaned code after having first read the ugly version and having gone through the work of understanding it. So now, as I read this version, I am annoyed because I already understand it and find the chopped-up functions and the instance variables redundant.
*Note // 11-09-25 16:34*
> Not only Uncle Bob finds this code annoying. Being forced to jump in a code just to understand pretty simple concepts is indeed annoying.
> There is a reason why this style is being disliked so much.

This annoyance is an issue that John Ousterhout³ and I have debated.⁴ When you understand an algorithm, the artifacts intended to help you understand it become annoying. Worse, if you understand an algorithm, the names or comments you write to help others will be biased by that understanding and may not help the reader as much as you think they will. A good example of that, in this code, is the addValueConsideringPrefix function. That name made perfect sense to me when I understood the algorithm. But it was a bit jarring two months later. Perhaps not as jarring as 49FNGO, but still not quite as obvious as I had hoped when I wrote it. It might have been better written as numbers.add(decrementValueIfThisCharIsaPrefix);, since that would be symmetrical with the numbers.add(value); in the nonprefixed case.
*Note // 11-09-25 14:08*
> While I do agree that https://buttondown.com/hillelwayne/archive/stroustrups-rule/ is a real thing: when you are familiar with a concept you want more terse syntax. 
> I don't believe this is the core issue with Martin's code. The issue is that it was suboptimally chopped up. 
> He has introduced unnessary visual and mental separation in a highly cohesive concepts like validation. 
> 
> I also don't like that he puts potential blame on comments. His code has none so far. 
> Arguably comments are significantly more lightweight tool for providing context around different invalid patterns:
> [todo: example how it should looklike]
> 
> - less code & less code constructs
> - easier to extend
> - zero runtime overhead
> 
> Clumsy or incomprehensible comments are significantly easier to ignore than incomprehensible verbose function names. 

You might complain that it’s longer. That’s true; but most of that extra length is due to the nicely named functions that explain what’s going on.
*Note // 11-09-25 16:39*
> This is the issue: none of complicated things are being explained in this version. 
> Arguably the new version takes more time to read adn understand

You might complain that the creation of an object is overkill and expensive. It’s true that allocating memory, and then garbage-collecting it later, takes extra time. There is also time spent in the calling of all those small methods. If you are writing embedded real-time code with very tight timing constraints, or if you are writing code for a high-performance flight simulator or first-person shooter game, then this decision could seem insane, not to mention unclean. But for my purposes, the extra time is the least of my concerns. I don’t need the speed. For my situation, I think trading speed for clarity, within reason, is a good trade to make.
*Note // 11-09-25 16:45*
> I'm not buying this philosophy. The second version of the code is wastefull (and sloppy) for no good reasons.
> Again the constraints martin tries to satisfy with the second version: 
> - minimize number of arguments in functions (you can clearly see that he is aming "zero arguments")
> - minimize size of functions
> - minimize usage of comments
> 
> This is his trade off. In order to satisfy them he is willing to sacrifies performance and redability. 
> 
> All three of this constraints are completely arbitrary. This is stylistic choice of his. And i can grant they make code
> immidiately recognizable as "clean code"(tm). But that is not a good thing.

You might be a functional programmer horrified that the functions are not “pure.” But, in fact, the static convert function is as pure as a function can be. The others are just little helpers that operate within a single invocation of that overarching pure function. Those instance variables are very convenient for allowing the individual methods to communicate without having to resort to passing arguments. This shows that one good use for an object is to allow the helper functions that operate within the execution of a pure function to easily communicate through the instance variables of the object.
*Note // 12-13-25 21:56*
> ololo!

You might be a functional programmer horrified that the functions are not “pure.” But, in fact, the static convert function is as pure as a function can be. The others are just little helpers that operate within a single invocation of that overarching pure function. Those instance variables are very convenient for allowing the individual methods to communicate without having to resort to passing arguments. This shows that one good use for an object is to allow the helper functions that operate within the execution of a pure function to easily communicate through the instance variables of the object.
*Note // 11-09-25 16:47*
> The code still is passing arguments. But now it is doing it implicitely. Which makes it easier to break and violate invariants. 
> Again no reasons. Just pure self inflicted pain

```
package fromRoman;

import java.util.Map;

public class FromRoman {
    private String roman;
    private static final String REGEX =
      "^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$";
    private static final Map<Character, Integer> VALUES = Map.of(
        'I', 1,
        'V', 5,
        'X', 10,
        'L', 50,
        'C', 100,
        'D', 500,
        'M', 1000
    );

    /** Constructs a FromRoman instance,
        validating the input Roman numeral. */
    public FromRoman(String roman) {
        if (!roman.matches(REGEX)) {
            throw new InvalidRomanNumeralException(roman);
        }
        this.roman = roman;
    }

    /** Converts the Roman numeral to an integer. */
    public int toInt() {
        int sum = 0;
        for (int i = 0; i < roman.length(); i++) {
            int currentValue = VALUES.get(roman.charAt(i));
            if (i < roman.length() - 1) {
                int nextValue = VALUES.get(roman.charAt(i + 1));
                if (currentValue < nextValue) {
                    sum -= currentValue;
                } else {
                    sum += currentValue;
                }
            } else {
                sum += currentValue;
            }
        }
        return sum;
    }

    /** Static method to convert a Roman numeral string to an integer. */
    public static int convert(String roman) {
        return new FromRoman(roman).toInt();
    }

    public static
    class InvalidRomanNumeralException extends RuntimeException {
        public InvalidRomanNumeralException(String roman) {
            super("Invalid Roman numeral: " + roman);
        }
    }
}
```

*Lines 5-6 // 12-13-25 22:03*
> I'm not sure why Robert Martin had chosen Grok.. but it's a good example how you can bias model to write suboptimal code. 
> This instance variable is completely unnessary. It is a bias that was introduced by his clean version. 
> 
> If you just ask model to create converter from "roman numerals to int in java" it would come up with pretty sane api: 
> 
> public final class RomanNumerals {
> 
>    public static int toInt(String roman) {...}
> }

Grok3 did a nice job. One might therefore question whether my effort of cleaning and testing was worth it. On the other hand, I, and not Grok3, am the final arbiter of the code that gets produced. The buck stops with me. I’m pleased that Grok3 was able to help, and I will be happy of that help when I ask for it. But in order to know that Grok3, or any LLM/AI, is giving me good code, it remains incumbent upon me to do the work to ensure that I can evaluate what it produces. Doing that work will usually involve writing and cleaning the code that I ask it to improve so that I can properly judge the outcome.
*Note // 11-09-25 17:01*
> this is such a canonical solution to the problem, that i don't believe grok did any kind of job here. 
> Just spit out the solution from training data.  

---

## Chapter 3. First Principles

Notice how the simplified getPreviousDayOfWeek function reads. It’s short, and it’s obvious. If you need to know more, all you have to do is look down to see the extracted functions. However, most often you would not need to know more. You’d simply look at that function, agree with it, and move on. It may not read like the prose of a Crichton novel, but it reads pretty well.
*Note // 11-09-25 17:31*
> this is just not true. None of introduced abstractions work in isolation. 
> 

Again, you may be concerned that we’re chopping this up into too many pieces, and that all those pieces will make the code harder to understand. But our assumption has been that this code is going to grow, and that we’re trying to make room for that growth. In any case, if you look at the directory structure, you’ll see that there’s a nice road map that will help anyone understand the organization.
*Note // 11-09-25 17:45*
> Guessing how the code will be growing is always bad idea. Programmers are pretty bad at guessing. 
> Right now we are just increasing complexity of the code without any reasons

```
———Statement.java———
package ubConferenceCenter;

public class Statement {
  …
  public void rent(CatalogItem item, int days) {
    int unitPrice = item.getUnitPrice();
    int price = calculatePrice(item, days, unitPrice);
    int thisTax = (int) Math.round(price * item.getTaxRate());
    items.add(item, days, unitPrice, price, thisTax);
    subtotal += price;
    tax += thisTax;
  }

  private int calculatePrice(CatalogItem item, int days, int unitPrice) {
    boolean eligibleForDiscount = item.isEligibleForDiscount(days);
    int price = unitPrice * days;
    if (eligibleForDiscount) price = (int) Math.round(price * .9);
    return price;
  }
 …
}

———ItemList.java———
package ubConferenceCenter;

import java.util.ArrayList;
import java.util.List;

public class ItemList {
  private List<RentalItem> items = new ArrayList<>();

  public void add(CatalogItem item, int days, int unitPrice,
                  int price, int thisTax) {
    items.add(new RentalItem(item.getName(), days,
              unitPrice, price, thisTax));
  }

  public RentalItem[] getItems() {
    boolean largeRoomFiveDays = items.stream().anyMatch(
      item -> item.type().equals("LARGE_ROOM") && item.days() == 5);
    boolean coffeeFiveDays = items.stream().anyMatch(
      item -> item.type().equals("COFFEE") && item.days() == 5);
    if (largeRoomFiveDays && coffeeFiveDays)
      items.add(new RentalItem("COOKIES", 5, 0, 0, 0));
    return items.toArray(new RentalItem[0]);
  }
}
```

*Line 18 // 11-09-25 17:46*
> why is discount hardcoded?!

*Line 45 // 11-09-25 17:52*
> I like this infinite cookies glitch! Just rent LARGE_ROOM with COFFEE for 5 days. and then ever time you call getItems()
> you get more and more cookies in the items list

One of the people who commented on the first edition of this book looked at code like this and called it “just dreadful.” You might be feeling that now too. But remember, we are expecting business growth, and we have decided that it is necessary to make room in this code for that growth.
*Note // 11-09-25 17:50*
> Ok. lets imagine requirements change: 
> 1) Dynamic discounts for items.
> 2) Bundle discounts. 
> 
> Is this new model really ready for these requirements? Or we are up to another redesign session ? :) 

```
———SmallRoom.java———
package ubConferenceCenter.catalogItems;

import ubConferenceCenter.CatalogItem;

public class SmallRoom implements CatalogItem {
  public String getName() {
    return "SMALL_ROOM";
  }

  public int getDiscountedPrice(int days) {
    double discountRate = (days == 5) ? 0.9 : 1.0;
    return (int) Math.round(getUnitPrice() * days * discountRate);
  }

  public int getUnitPrice() {
    return 100;
  }

  public double getTaxRate() {
    return 0.05;
  }
}

———LargeRoom.java___
package ubConferenceCenter.catalogItems;

import ubConferenceCenter.CatalogItem;

public class LargeRoom implements CatalogItem {
  public int getDiscountedPrice(int days) {
    double discountRate = (days == 5) ? 0.9 : 1.0;
    return (int) Math.round(getUnitPrice() * days * discountRate);
  }

  public int getUnitPrice() {
    return 150;
  }

  public double getTaxRate() {
    return 0.05;
  }

  public String getName() {
    return "LARGE_ROOM";
  }
}

———Coffee.java———
package ubConferenceCenter.catalogItems;

import ubConferenceCenter.CatalogItem;

public class Coffee implements CatalogItem {
  public int getDiscountedPrice(int days) {
    return getUnitPrice() * days;
  }

  public int getUnitPrice() {
    return 10;
  }

  public double getTaxRate() {
    return 0;
  }

  public String getName() {
    return "COFFEE";
  }
}

———Cookies.java———
package ubConferenceCenter.catalogItems;

import ubConferenceCenter.CatalogItem;

public class Cookies implements CatalogItem {
  public int getDiscountedPrice(int days) {
    return getUnitPrice() * days;
  }

  public int getUnitPrice() {
    return 15;
  }

  public double getTaxRate() {
    return 0;
  }

  public String getName() {
    return "COOKIES";
  }
}

———CookieBonus.java———
package ubConferenceCenter.bonuses;

import ubConferenceCenter.Bonus;
import ubConferenceCenter.RentalItem;

import java.util.List;

public class CookieBonus implements Bonus {
  public void checkAndAdd(List<RentalItem> items,
                          List<RentalItem> bonusItems) {
    boolean largeRoomFiveDays = items.stream().anyMatch(
      item -> item.type().equals("LARGE_ROOM") && item.days() == 5);
    boolean coffeeFiveDays = items.stream().anyMatch(
      item -> item.type().equals("COFFEE") && item.days() == 5);
    if (largeRoomFiveDays && coffeeFiveDays)
      bonusItems.add(new RentalItem("COOKIES", 5, 0, 0, 0));
  }
}
```

*Line 111 // 11-09-25 17:56*
> Inifinite COOKIES glitch. yay!

Imagine that this code was written in JavaScript as opposed to Java. Imagine that it will be sent to a browser to run there. The fact that we have divided it into two components has a very specific advantage. If one of those two components changes, and if the browser maintains a cache of those components, then only the component that changed needs to be reloaded into the browser. On slow network connections, that could be a big advantage.
*Note // 11-09-25 17:58*
> we are talking of less that 1 KiB worth of data. Even in the times of 2400 modems it would not be a big deal.

This major cleaning will greatly facilitate the growth of the project. The time it took will be paid back many times over simply because there’s a clear and obvious place to put the needed changes; and those changes aren’t likely to interfere with each other or with the overall flow of the system.
*Note // 11-09-25 18:03*
> this chapter is one big ball of a mess. It shows that you can always blow up complexity of your code base and then still 
> not be able to satisfy new requirements because your guess was wrong. 
> 
> I don't believe that this code is designed for scaling and growing. It's still not generic enough. Infinite cookie glitch
> is good though. Cookie Monster approves.
> Using floats for calculating taxes is also very nice. IRS might not approve that though
> 
> Todo: I need to find a way to represent final result somehow. it've very big and clumsy

---

## Chapter 4. Meaningful Names

Our rule for functions is exactly the opposite. The length of their names should be inversely proportional to the length of their scope. Thus, global functions will have short names, while private methods within a class will have relatively long names. Tests, which have the shortest scope of all (effectively zero), have the longest names of all.
*Note // 11-09-25 18:06*
> I don't know where this rule is coming from. 

---

## Chapter 5. Comments

The proper use of comments is to compensate for our failure to express ourselves in code. Note that I used the word failure. I meant it. Comments are always failures of either our languages or our abilities.
*Note // 11-09-25 20:38*
> i would generalize even more: this is a failure of our universe. 
> If only laws of our universe was better, we could avoid writing comments

It has been said that advice like this gives license to younger programmers to avoid writing comments. Shame on any programmers, young or old, who use these recommendations as an excuse for their laziness and/or unwillingness to add proper explanations and context. In the end, I cannot concern myself with how others might abuse my advice. My sole purpose here is help those who will not.
*Note // 11-09-25 20:51*
> this is nice kumbaya, but this is very disengeneous. instead of showing how to write good comments, Martin
> emphasise all cases of bad comments and propose rewrite comments in code. 
> Majority of examples of "good" comments are actually bad comments, which makes me even more convinced that Martin
> just doesn't know how to use comments. 
> [show snippets of "good" comments from below]
> 
> The genuine approach would be emphasise how to write good comments and show cases when writing comments is unavoidable. 

One of the more common motivations for writing comments is bad code. We write a module and we know it is confusing and disorganized. We know it’s a mess. So we say to ourselves, “Ooh, I’d better comment that!”
*Note // 11-09-25 20:53*
> Seriously: if you have a messy code, sit and try to write clear comments - it will clarify your thinking and might help you
> reorganize original mess. 
> Expressing thoughts in code is harder than expressing thoughts in plain english: express though in code require clear thinking
> and mastery of the language and expressiveness of the language. 

```
if (employee.isEligibleForFullBenefits())
```

*Line 1 // 11-09-25 20:35*
> fundamentally this not the same code!

Why did the author resort to a comment instead of giving the method a better name like responderBeingTested? And why would I allow this comment rather than changing the name of the method? That’s because the author was using the well-known Singleton² design pattern. When you use a design pattern like that, you should follow the canonical form specified by that pattern. In the Singleton pattern, the name of the accessor function always ends in the word Instance. So the author decided to fall back on a comment, rather than abandon the canonical form of the pattern.
*Note // 11-09-25 20:44*
> this doesn't look like a canonical singletone instance. 
> 
> This is how typical singletone should look like. 
> 
> class Responder {
> 
>    public static final Responder INSTANCE = createResponder();
> }
> 
> Assuming that suffix `Instance` is enough for singletone assumption is just wrong

```
   public void testCompareTo() throws Exception
   {
     WikiPagePath a = PathParser.parse("PageA");
     WikiPagePath ab = PathParser.parse("PageA.PageB");
     WikiPagePath b = PathParser.parse("PageB");
     WikiPagePath aa = PathParser.parse("PageA.PageA");
     WikiPagePath bb = PathParser.parse("PageB.PageB");
     WikiPagePath ba = PathParser.parse("PageB.PageA");

     assertTrue(a.compareTo(a) == 0);    // a == a
     assertTrue(a.compareTo(b) != 0);    // a != b
     assertTrue(ab.compareTo(ab) == 0);  // ab == ab
     assertTrue(a.compareTo(b) == -1);   // a < b
     assertTrue(aa.compareTo(ab) == -1); // aa < ab
     assertTrue(ba.compareTo(bb) == -1); // ba < bb
     assertTrue(b.compareTo(a) == 1);    // b < a
     assertTrue(ab.compareTo(aa) == 1);  // ab > aa
     assertTrue(bb.compareTo(ba) == 1);  // bb > ba
   }
```

*Lines 10-19 // 11-09-25 20:55*
> add rant that java is dog water language when it comes to expressiveness. 

```
     catch(IOException e)
     {
       LoadedProperties.loadDefaults();
     }
```

*Lines 1-4 // 11-09-25 20:57*
> this is fundamentally not the same code! and does something completely different. 
> both examples are terrible at handling exceptions

```
   private void startSending()
   {
     try
     {
       doSending();
     }
     catch(SocketException e)
     {
       // normal. someone stopped the request.
     }
     catch(Exception e)
     {
       addExceptionAndCloseResponse(e);
     }
   }
  
   private void addExceptionAndCloseResponse(Exception e)
   {
     try
     {
       response.add(ErrorResponder.makeExceptionString(e));
       response.closeAll();
     }
     catch(Exception e1)
     {
     }
   }
```

*Lines 1-27 // 11-09-25 20:59*
> this is a dog water of handling exceptions

This is just graffiti. Please don’t spray-paint your name all over the source code. Source code control systems are very good at remembering who added what, when. There is no need to pollute the code with little bylines. You might think that such comments would be useful in order to help others know who to talk to about the code. But the reality is that they tend to stay around for years and years, getting less and less accurate and relevant. The same can be said for PR numbers and JIRA tags. Put those in commit comments, not in the source code.
*Note // 11-09-25 21:05*
> I'm surprised to hear this argument from IT veteran who for sure should have seen migration from CVS to SVN and from SVN to GIT.
> And how much authorship information is lost during such migrations. 
> 

Others who see that commented-out code will be unlikely to have the courage to delete it. They’ll think it is there for a reason and is too important to delete. So commented-out code gathers like dregs at the bottom of a bad bottle of wine. Consider this from Apache Commons:
*Note // 11-09-25 21:02*
> in reallity that is not true, people are pretty comfortable removing OLD commented out code. 
> And comments are amazing instrument to disable code that want to keep for whatever reason. 
> 
> It sounds smart to use code control system for this, but reallity is comments ARE WAY MORE convinient.  

---

## Chapter 7. Clean Functions

```
if(employee.shouldBePaidToday())

  employee.pay();
```

*Lines 1-3 // 11-09-25 22:08*
> this is a good example that writing good code require not only clear thinking, but master of the language and support
> from the language. here to have this code like this in java we would have to stuff Employee class with 100500 different concerns

```
public static int orientation(Point a, Point b, Point c) {
  double val = (b.x() - a.x()) * (c.y() - a.y()) -
               (b.y() - a.y()) * (c.x() - a.x());
  return sign(val);
}

private static int sign(double n) {
  if (n == 0) {
    return 0;
  }
  return (n > 0) ? +1 : -1;
}
```

*Line 7 // 11-09-25 21:36*
> this is pretty bad signature

Now we’ve got instance variables and all manner of variable manipulations. And yet, the sigma function is pure. None of those impure operations are visible outside the sigma function.
*Note // 11-09-25 21:44*
> the sigma function is not pure: it can throw exception
> But what worse - SigmaCalculator is abysmass. 

```
void openAndDo(char* fileName, void (*doTo)(FILE*)) {
  FILE* f = fopen(fileName, "r+");
  doTo(f);
  fclose(f);
}
```

*Lines 1-5 // 11-09-25 22:23*
>  this is complete misunderstanding what pure means. the problem is not only fopen leaves sysem in modified state.
> it is also the fact that fopen depends on external state of the system (file system in this case)
> and it is not guaranteed to have exactly same execution with the same input parameters
>  
> In addition purity implies referential transparency - i.e. we can replace function call with return value and nothing should
>  change in the result of the program
>  
> the result of appendLineEnd is void. referential transparency implies that we can just remove call to this function and the
> program would generate the same results.
> this is objectively false

---

## Chapter 8. Function Heuristics

In the first edition of this book, this statement confused a number of people. Their complaint was that a function with an empty argument list can do little other than return a constant. So, apparently, some further explanation is needed.
*Note // 11-09-25 22:30*
> pure functions! Что этот ебанный дед несет! 
> Люди жаловались что функция без сайд эффектов и без параметров мало что может сделать

Since then, the definition has been generalized. Nowadays, we define a side effect to be any state change that outlives the function, even if that state change is the primary intent of the function. In other words, a function with a side effect is impure.
*Note // 11-10-25 10:51*
> this is wrong!

The point is that the facilities of most OOPLs provide a handy way to hide side effects behind encapsulation boundaries, thus preventing temporal couplings from leaking out into the application at large.
*Note // 11-10-25 00:17*
> this is again complete misuderstanding what purity implies. the concurrency note is funny, did he read my critique? :)
> 
> overall with global mutable state you can not avoid impurity

---

## Chapter 9. The Clean Method

We will begin with an imaginary Python application: the Tax Calculator for the mythical state of Bobolia. It’s been a long time since I’ve written any Python, so bear with me.
*Note // 11-10-25 11:40*
> This is a whimsical chapter...but the lessons it teaches is that you can write java in any language, including python. 
> this is not a good lesson. 
> The test should be table tests
> 
> this is questionable decision to write book using language that you don't really know <- need to communicate this point

At this point, you might be accusing this design of classitis²—the tendency to create too many classes and thereby complicate the design. I disagree. The three new classes do not add any complexity that wasn’t already there. They just move that complexity into nicely named places.
*Note // 11-10-25 11:36*
> this is wrong. [todo: put an image of jigsaw puzzle]

---

## Chapter 10. One Thing

Extract till you drop!
*Note // 11-10-25 11:41*
> this is the main mechanism of creating "clean code" style

When Algol, and later, C, came along, the idea of functions burst upon us like a wave. Suddenly we could create modules that could be individually compiled. Modular programming became all the rage.³
*Note // 11-10-25 11:44*
> this is a good example of yapping that has nothing to do with the point argued

And this is the solution for the “sea of tiny little functions” fear. You aren’t going to drown beneath that sea, because there won’t be a sea. Instead, you’ll have a nicely arranged and nicely named hierarchy of namespaces and classes into which you can tuck your nicely named functions. Those nice names will be the guideposts that other programmers will use to locate your functions. Better still, those guideposts will provide the context that other programmers will use to understand your functions.
*Note // 11-10-25 12:01*
> this is the crux of the argument. but this doesn't work cause in languages like java you have limited depth of hierarchy: 
> artifact -> package -> class -> method
> 
> The idea of limiting number of items of type is that it improves navigation and avoid exhausting working memory, which
> can not hold more than 4-7 items at a time. 
> 
> At every hierarchy level you want to limit amount of stuff that is required to be mentally consumed. 
> 
> If you have a long method - you can treat it as a black box as the method has just one entry point, you can ignore the body. 
> 
> If your class have many methods - you can not treat it as the black box as class can have many entry points - many public mehtods. 
> You can not treat package with many classes as a black box, you can not treat artifact with many packages as black box. 
> 
> Essentially method body is the only place where you can contain and isolate complexity without affecting other parts of code. 
> 
> In languages like javascript & scala you can have functions declared inside functions. 
> 
> And sometimes this is usefull. But sometimes it just silly re-arrengement that doesn't buy you much. [todo example]

Now take a new programmer who’d never seen gi. Ask that poor sod to change the scaling on the x-axis. Where the hell should he go? He doesn’t know where the watering hole is. He’s got no idea that there’s a saber-toothed tiger on the loose in that valley over there. He’s just going to fumble around inside that 3,000-line morass of code until, by luck, he stumbles upon something that looks like it might set the scaling on the x-axis.
*Note // 11-10-25 12:03*
> the solution is easy - don't write 3000-line methods. Or provide navigation map - like comments. 
> You can organize code inside methods it doesn't have to be a mess. 

If this entanglement is severe—that is if there are several different facts established by the higher-level function that you must keep in mind while reading the lower-level function—then there is a reasonable chance that the extraction might not be worth doing. However, if the entanglement is minimal, then the fact that the extracted function has a nice descriptive name and that fact that it appears directly after its parent function will likely make the extraction worthwhile.
*Note // 11-10-25 12:11*
> Martin uses "nice" a lot in this chapter. And this niceness is very subjective. I don't find majority of his code to be
> nicely named. Because quite often they violate Bob Nystrom naming principles. [todo: add examples]

Or to say this even more differently: It’s a judgment call. Choose wisely.
*Note // 11-10-25 12:09*
> I would agree with this. And if you're extracting every if and while statements in their own methods - that is a bad judgement  

Every large function is really hiding a class (or more) inside it.
*Note // 11-10-25 12:15*
> I would improve wording "every large function CAN be represented as a class, but not every function SHOULD be"

First come the tests, which Martin did not include in his book so long ago.
*Note // 11-10-25 12:12*
> the point about martin writing java in go. this is what makes this book more frustrating than first edition. he writes
> very non-idiomatic go and python. which obsures the result even more

This makes things just a little bit cleaner. In a more complex module, this could make things a lot cleaner. However, there is a risk. Those static variables could get corrupted in a multithreaded environment. This is easy enough to correct by changing all the functions and variables from static to instance methods and variables. But I’m not going to worry about that for this example.
*Note // 11-10-25 12:28*
> why?! why introduce broken code and say that you wont worry about it ?! 
> Any junior person would read the code and remember this broken pattern. 
> There is so much talk about cleaning in this book and yet the author is just being lazy in fixing the most basic design mistakes

The bottom line is that sometimes when we extract functions, it is convenient and clean to allow those functions to communicate through variables in the scope (the class) that contains them.
*Note // 11-10-25 12:28*
> it is "convinient and lazy". there is nothing clean about it

```
(defn quad [a b c]
  (let [discriminant (- (* b b) (* 4 a c))
        linear-solution
        (fn []
          (if (zero? b)
            []
            [(/ (- c) b)]))

        single-solution
        (fn []
          [(/ (- b) (* 2 a))])

        two-solutions
        (fn []
          (let [sqrt-desc (Math/sqrt discriminant)
                x1 (/ (+ (- b) sqrt-desc) (* 2 a))
                x2 (/ (- (- b) sqrt-desc) (* 2 a))]
            [x1 x2]))]

    (cond
      (zero? a) (linear-solution)
      (zero? discriminant) (single-solution)
      (neg? discriminant) []
      :else (two-solutions))))
```

*Lines 1-24 // 11-10-25 12:46*
> this is just re-shuffling. zero things were improved. i do recommend tweaking and playing around with code you want to undertand
> just don't commit those changes

To some extent this is a matter of style and preference. You may like your functions a bit bigger than I like mine. Perhaps you are more comfortable with 20-line functions or 25-line functions. That’s up to you.
*Note // 11-10-25 12:46*
> this is only matter of style. This approach will make your code immidiately recognizable as "clean code"(tm). 

---

## Foreword

Micah Daniel Martin
*Note // 11-09-25 10:39*
> this is cringe!