@booklib/skills 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/animation-at-work/SKILL.md +246 -0
  4. package/animation-at-work/assets/example_asset.txt +1 -0
  5. package/animation-at-work/references/api_reference.md +369 -0
  6. package/animation-at-work/references/review-checklist.md +79 -0
  7. package/animation-at-work/scripts/example.py +1 -0
  8. package/bin/skills.js +85 -0
  9. package/clean-code-reviewer/SKILL.md +292 -0
  10. package/clean-code-reviewer/evals/evals.json +67 -0
  11. package/data-intensive-patterns/SKILL.md +204 -0
  12. package/data-intensive-patterns/assets/example_asset.txt +1 -0
  13. package/data-intensive-patterns/references/api_reference.md +34 -0
  14. package/data-intensive-patterns/references/patterns-catalog.md +551 -0
  15. package/data-intensive-patterns/references/review-checklist.md +193 -0
  16. package/data-intensive-patterns/scripts/example.py +1 -0
  17. package/data-pipelines/SKILL.md +252 -0
  18. package/data-pipelines/assets/example_asset.txt +1 -0
  19. package/data-pipelines/references/api_reference.md +301 -0
  20. package/data-pipelines/references/review-checklist.md +181 -0
  21. package/data-pipelines/scripts/example.py +1 -0
  22. package/design-patterns/SKILL.md +245 -0
  23. package/design-patterns/assets/example_asset.txt +1 -0
  24. package/design-patterns/references/api_reference.md +1 -0
  25. package/design-patterns/references/patterns-catalog.md +726 -0
  26. package/design-patterns/references/review-checklist.md +173 -0
  27. package/design-patterns/scripts/example.py +1 -0
  28. package/domain-driven-design/SKILL.md +221 -0
  29. package/domain-driven-design/assets/example_asset.txt +1 -0
  30. package/domain-driven-design/references/api_reference.md +1 -0
  31. package/domain-driven-design/references/patterns-catalog.md +545 -0
  32. package/domain-driven-design/references/review-checklist.md +158 -0
  33. package/domain-driven-design/scripts/example.py +1 -0
  34. package/effective-java/SKILL.md +195 -0
  35. package/effective-java/assets/example_asset.txt +1 -0
  36. package/effective-java/references/api_reference.md +1 -0
  37. package/effective-java/references/items-catalog.md +955 -0
  38. package/effective-java/references/review-checklist.md +216 -0
  39. package/effective-java/scripts/example.py +1 -0
  40. package/effective-kotlin/SKILL.md +225 -0
  41. package/effective-kotlin/assets/example_asset.txt +1 -0
  42. package/effective-kotlin/references/api_reference.md +1 -0
  43. package/effective-kotlin/references/practices-catalog.md +1228 -0
  44. package/effective-kotlin/references/review-checklist.md +126 -0
  45. package/effective-kotlin/scripts/example.py +1 -0
  46. package/kotlin-in-action/SKILL.md +251 -0
  47. package/kotlin-in-action/assets/example_asset.txt +1 -0
  48. package/kotlin-in-action/references/api_reference.md +1 -0
  49. package/kotlin-in-action/references/practices-catalog.md +436 -0
  50. package/kotlin-in-action/references/review-checklist.md +204 -0
  51. package/kotlin-in-action/scripts/example.py +1 -0
  52. package/lean-startup/SKILL.md +250 -0
  53. package/lean-startup/assets/example_asset.txt +1 -0
  54. package/lean-startup/references/api_reference.md +319 -0
  55. package/lean-startup/references/review-checklist.md +137 -0
  56. package/lean-startup/scripts/example.py +1 -0
  57. package/microservices-patterns/SKILL.md +179 -0
  58. package/microservices-patterns/references/patterns-catalog.md +391 -0
  59. package/microservices-patterns/references/review-checklist.md +169 -0
  60. package/package.json +17 -0
  61. package/refactoring-ui/SKILL.md +236 -0
  62. package/refactoring-ui/assets/example_asset.txt +1 -0
  63. package/refactoring-ui/references/api_reference.md +355 -0
  64. package/refactoring-ui/references/review-checklist.md +114 -0
  65. package/refactoring-ui/scripts/example.py +1 -0
  66. package/storytelling-with-data/SKILL.md +238 -0
  67. package/storytelling-with-data/assets/example_asset.txt +1 -0
  68. package/storytelling-with-data/references/api_reference.md +379 -0
  69. package/storytelling-with-data/references/review-checklist.md +111 -0
  70. package/storytelling-with-data/scripts/example.py +1 -0
  71. package/system-design-interview/SKILL.md +213 -0
  72. package/system-design-interview/assets/example_asset.txt +1 -0
  73. package/system-design-interview/references/api_reference.md +582 -0
  74. package/system-design-interview/references/review-checklist.md +201 -0
  75. package/system-design-interview/scripts/example.py +1 -0
  76. package/using-asyncio-python/SKILL.md +242 -0
  77. package/using-asyncio-python/assets/example_asset.txt +1 -0
  78. package/using-asyncio-python/references/api_reference.md +267 -0
  79. package/using-asyncio-python/references/review-checklist.md +149 -0
  80. package/using-asyncio-python/scripts/example.py +1 -0
  81. package/web-scraping-python/SKILL.md +259 -0
  82. package/web-scraping-python/assets/example_asset.txt +1 -0
  83. package/web-scraping-python/references/api_reference.md +393 -0
  84. package/web-scraping-python/references/review-checklist.md +163 -0
  85. package/web-scraping-python/scripts/example.py +1 -0
@@ -0,0 +1,1228 @@
1
+ # Effective Kotlin — Practices Catalog
2
+
3
+ Complete catalog of all 52 items from *Effective Kotlin* (2nd Edition) by Marcin Moskała,
4
+ organized by part and chapter.
5
+
6
+ ---
7
+
8
+ ## Part 1: Good Code
9
+
10
+ ### Chapter 1: Safety (Items 1–10)
11
+
12
+ #### Item 1: Limit Mutability
13
+
14
+ **Category:** Safety — Mutability Control
15
+
16
+ **Core Practice:** Prefer read-only properties (val), immutable collections, and data class
17
+ copy() over mutable state. Mutable state makes reasoning about code harder, introduces
18
+ concurrency risks, and increases the surface for bugs.
19
+
20
+ **Key Techniques:**
21
+ - Use `val` instead of `var` wherever possible
22
+ - Use read-only collection interfaces (`List`, `Map`, `Set`) instead of mutable variants
23
+ - Use `data class` with `copy()` for state updates instead of mutating fields
24
+ - If mutation is needed, limit its scope: prefer local mutable variables over mutable properties
25
+ - For shared mutable state, use synchronization or concurrent data structures
26
+ - Prefer `Sequence` or `Flow` (which produce new elements) over mutable accumulation
27
+
28
+ **When to Apply:** Always — this is the default stance. Only introduce mutability when
29
+ there's a clear performance or API need.
30
+
31
+ **Anti-Pattern:** Public `var` properties, returning `MutableList` from functions, using
32
+ mutable collections as class properties without encapsulation.
33
+
34
+ ---
35
+
36
+ #### Item 2: Minimize the Scope of Variables
37
+
38
+ **Category:** Safety — Scope Control
39
+
40
+ **Core Practice:** Declare variables in the tightest scope possible and prefer defining
41
+ variables close to their first usage. Use `if`, `when`, `let`, and `run` to tighten scope.
42
+
43
+ **Key Techniques:**
44
+ - Declare variables inside the block where they're used, not at the top of a function
45
+ - Use destructuring declarations to limit what's visible
46
+ - Prefer `let`/`run` scoping functions to restrict a value's visibility
47
+ - Use `when` with variable binding: `when (val result = compute()) { ... }`
48
+ - For loops, prefer `for` with a narrow iteration variable over external counters
49
+ - Prefer `val` with conditional initialization over `var` with later reassignment
50
+
51
+ **When to Apply:** Always — every variable declaration should be as close to first use and
52
+ as narrow in scope as possible.
53
+
54
+ **Anti-Pattern:** Declaring all variables at the top of a function; using `var` initialized
55
+ to a default and reassigned in a branch.
56
+
57
+ ---
58
+
59
+ #### Item 3: Eliminate Platform Types as Soon as Possible
60
+
61
+ **Category:** Safety — Null Safety at Boundaries
62
+
63
+ **Core Practice:** When calling Java code, the return types are "platform types" (noted as
64
+ `Type!`) where nullability is unknown. Specify nullability explicitly at the call site to
65
+ prevent NPEs from propagating through Kotlin code.
66
+
67
+ **Key Techniques:**
68
+ - Annotate Java code with `@Nullable`/`@NotNull` (JSR-305, JetBrains, AndroidX annotations)
69
+ - When consuming Java APIs, declare the expected nullability explicitly: `val name: String? = javaObj.getName()`
70
+ - Never let platform types leak into public Kotlin APIs — always resolve them
71
+ - Write wrapper functions around Java APIs with explicit nullability
72
+
73
+ **When to Apply:** At every Java/Kotlin boundary. Critical for Android development and
74
+ mixed-language projects.
75
+
76
+ **Anti-Pattern:** Letting platform types propagate as inferred types throughout Kotlin code;
77
+ assuming Java return values are non-null without verification.
78
+
79
+ ---
80
+
81
+ #### Item 4: Do Not Expose Inferred Types
82
+
83
+ **Category:** Safety — Type Clarity
84
+
85
+ **Core Practice:** When a public function or property's type is inferred, changes to the
86
+ implementation can inadvertently change the public type. Always specify types explicitly for
87
+ public/protected API members.
88
+
89
+ **Key Techniques:**
90
+ - Explicit return types on all public/protected functions and properties
91
+ - Explicit types for all public constants and companion object properties
92
+ - Allow inference only for local variables and private members where the type is obvious
93
+ - Be especially careful with factory functions — the inferred return type may be too specific
94
+
95
+ **When to Apply:** For all public API surfaces. Local variables and private members can
96
+ use inference when the type is obvious from context.
97
+
98
+ **Anti-Pattern:** `fun createAnimal() = Dog()` — infers `Dog` instead of desired `Animal`.
99
+
100
+ ---
101
+
102
+ #### Item 5: Specify Your Expectations on Arguments and State
103
+
104
+ **Category:** Safety — Preconditions and Contracts
105
+
106
+ **Core Practice:** Use `require()`, `check()`, and `assert()` to document and enforce
107
+ expectations at function boundaries. Fail fast with clear messages.
108
+
109
+ **Key Techniques:**
110
+ - `require(condition) { "message" }` — for argument validation; throws `IllegalArgumentException`
111
+ - `check(condition) { "message" }` — for state validation; throws `IllegalStateException`
112
+ - `assert(condition)` — for assertions checked only in testing (-ea flag)
113
+ - `requireNotNull(value) { "message" }` and `checkNotNull(value) { "message" }` — for null checks that smart-cast
114
+ - Place preconditions at the top of functions, before any logic
115
+ - Use the `contract` mechanism for custom smart-cast functions
116
+
117
+ **When to Apply:** At public function boundaries, constructors, and any place where
118
+ invariants must hold. Use `require` for input validation, `check` for state validation.
119
+
120
+ **Anti-Pattern:** Proceeding with invalid arguments and failing deep in the call stack
121
+ with unclear errors; custom exception types where standard ones suffice.
122
+
123
+ ---
124
+
125
+ #### Item 6: Prefer Standard Errors to Custom Ones
126
+
127
+ **Category:** Safety — Error Types
128
+
129
+ **Core Practice:** Use standard Kotlin/Java exception types (`IllegalArgumentException`,
130
+ `IllegalStateException`, `UnsupportedOperationException`, `ConcurrentModificationException`,
131
+ `NoSuchElementException`, `IndexOutOfBoundsException`) rather than defining custom exception
132
+ hierarchies for common error conditions.
133
+
134
+ **Key Techniques:**
135
+ - `require` throws `IllegalArgumentException` — use for argument validation
136
+ - `check` throws `IllegalStateException` — use for state validation
137
+ - Custom exceptions only when callers need to catch and handle specific error types differently
138
+ - In library code, document which exceptions can be thrown
139
+
140
+ **When to Apply:** Default to standard exceptions. Create custom exceptions only when
141
+ the caller needs to distinguish between different error conditions programmatically.
142
+
143
+ **Anti-Pattern:** Creating `UserNotFoundException`, `InvalidEmailException`, etc. when
144
+ `IllegalArgumentException` with a descriptive message would suffice.
145
+
146
+ ---
147
+
148
+ #### Item 7: Prefer Null or Failure Result When Lack of Result Is Possible
149
+
150
+ **Category:** Safety — Expected Failure Handling
151
+
152
+ **Core Practice:** For functions where failure is expected (not exceptional), return `null`
153
+ or a `Result`/sealed class instead of throwing exceptions. Reserve exceptions for truly
154
+ exceptional situations.
155
+
156
+ **Key Techniques:**
157
+ - Return `T?` when the "no result" case is simple (e.g., `find` operations)
158
+ - Return `Result<T>` or a sealed class when the caller needs to know *why* it failed
159
+ - Use sealed class hierarchies: `sealed interface Result<T> { data class Success<T>(val value: T) : Result<T>; data class Failure<T>(val error: Error) : Result<T> }`
160
+ - Naming convention: `getOrNull()`, `findOrNull()` for nullable returns
161
+ - Functions named `getX()` may throw; functions named `getXOrNull()` return null
162
+ - Use `runCatching { }` to convert throwing code to `Result`
163
+
164
+ **When to Apply:** For operations that can legitimately fail: parsing, lookups, network
165
+ calls, user input validation. NOT for programming errors (those should throw).
166
+
167
+ **Anti-Pattern:** Throwing exceptions for expected failure paths like "user not found" or
168
+ "invalid format"; using exceptions for flow control.
169
+
170
+ ---
171
+
172
+ #### Item 8: Handle Nulls Properly
173
+
174
+ **Category:** Safety — Null Handling
175
+
176
+ **Core Practice:** Kotlin's null-safety system is powerful but must be used correctly.
177
+ Prefer safe calls, smart casting, Elvis operator, and `let` over force-unwrapping (`!!`).
178
+
179
+ **Key Techniques:**
180
+ - Safe call: `user?.name` — returns null if user is null
181
+ - Elvis operator: `user?.name ?: "Unknown"` — provides default for null
182
+ - Smart casting: `if (user != null) { user.name }` — compiler tracks non-null
183
+ - `let` for null-safe scoping: `user?.let { sendEmail(it) }`
184
+ - `lateinit` for properties that can't be initialized in constructor but are set before use
185
+ - `Delegates.notNull<T>()` — alternative to lateinit for primitives
186
+ - Avoid `!!` except where you can prove the value is non-null and want to fail fast
187
+
188
+ **When to Apply:** Every time you encounter a nullable type. Default to safe handling.
189
+
190
+ **Anti-Pattern:** Widespread `!!` usage; nested null checks instead of safe calls;
191
+ checking for null then using `!!` in the next line.
192
+
193
+ ---
194
+
195
+ #### Item 9: Close Resources with use
196
+
197
+ **Category:** Safety — Resource Management
198
+
199
+ **Core Practice:** Use the `use` extension function (Kotlin's equivalent of try-with-resources)
200
+ for any `Closeable`/`AutoCloseable` resource to ensure proper cleanup.
201
+
202
+ **Key Techniques:**
203
+ - `FileReader(path).use { reader -> reader.readText() }`
204
+ - `BufferedReader(reader).use { it.readLine() }` — `it` reference in simple cases
205
+ - `File(path).useLines { lines -> lines.filter { ... }.toList() }` — for line-by-line processing
206
+ - `use` works with any `Closeable`: streams, connections, database cursors
207
+ - Nest `use` calls for multiple resources, or use extension functions to flatten
208
+
209
+ **When to Apply:** Every time you open a file, database connection, network stream,
210
+ or any other closeable resource.
211
+
212
+ **Anti-Pattern:** Manual try/finally for resource cleanup; forgetting to close resources
213
+ in error paths; using `readLines()` for large files instead of `useLines()`.
214
+
215
+ ---
216
+
217
+ #### Item 10: Write Unit Tests
218
+
219
+ **Category:** Safety — Testing
220
+
221
+ **Core Practice:** Unit tests are the primary mechanism for verifying correctness and
222
+ preventing regressions. Write tests for all non-trivial business logic.
223
+
224
+ **Key Techniques:**
225
+ - Test public API contracts, not implementation details
226
+ - Use descriptive test names that explain the scenario and expected outcome
227
+ - Structure tests as Arrange-Act-Assert (Given-When-Then)
228
+ - Test edge cases: empty inputs, null values, boundary conditions
229
+ - Test error paths: verify correct exceptions or Result.failure
230
+ - Use parameterized tests for multiple input/output combinations
231
+ - Mock external dependencies, but prefer fakes/stubs over mocking frameworks
232
+ - Aim for meaningful coverage of business logic, not just line coverage numbers
233
+
234
+ **When to Apply:** For all modules with business logic. Less critical for simple
235
+ data classes or pass-through wrappers.
236
+
237
+ **Anti-Pattern:** No tests; tests that test framework behavior instead of business logic;
238
+ tests tightly coupled to implementation; testing only the happy path.
239
+
240
+ ---
241
+
242
+ ### Chapter 2: Readability (Items 11–18)
243
+
244
+ #### Item 11: Design for Readability
245
+
246
+ **Category:** Readability — General
247
+
248
+ **Core Practice:** Code is read far more often than it is written. Optimize for the reader,
249
+ not the writer. Prefer clear, obvious code over clever, concise code.
250
+
251
+ **Key Techniques:**
252
+ - Prefer explicit over implicit: clear names, explicit types where helpful, named arguments
253
+ - Avoid clever tricks: nested scope functions, complex lambda chains, operator abuse
254
+ - Keep functions short and focused on a single level of abstraction
255
+ - Use intermediate variables with descriptive names to break complex expressions
256
+ - Prefer `if`/`when` expressions with clear branches over complex ternary-style logic
257
+ - Code should read like well-written prose — a developer should understand it on first read
258
+
259
+ **When to Apply:** Always. Every code decision should consider "will the next reader
260
+ understand this immediately?"
261
+
262
+ **Anti-Pattern:** Chaining multiple scope functions; using `also` inside `let` inside `apply`;
263
+ excessively compact code that requires mental unpacking.
264
+
265
+ ---
266
+
267
+ #### Item 12: Operator Meaning Should Be Consistent with Its Function Name
268
+
269
+ **Category:** Readability — Operators
270
+
271
+ **Core Practice:** Kotlin allows operator overloading, but each operator has a conventional
272
+ meaning tied to its function name (`plus`, `times`, `contains`, etc.). Don't overload
273
+ operators with meanings that violate these conventions.
274
+
275
+ **Key Techniques:**
276
+ - `plus` should represent addition or combination, not unrelated operations
277
+ - `times` should represent multiplication or repetition
278
+ - `contains` (the `in` operator) should check membership
279
+ - `get`/`set` (index operators) should provide indexed access
280
+ - `invoke` should "call" or "execute" the object
281
+ - If the operation doesn't naturally match the operator's name, use a named function instead
282
+ - It's acceptable to use operators on domain types when the meaning is universally clear (e.g., `Vector + Vector`)
283
+
284
+ **When to Apply:** Whenever defining operator overloads. Ask: "Does this operation match
285
+ what `plus`/`times`/`contains`/etc. conventionally means?"
286
+
287
+ **Anti-Pattern:** Using `+` to mean "add to database"; using `*` to mean "format string";
288
+ using `invoke` for operations that aren't conceptually "calling" the object.
289
+
290
+ ---
291
+
292
+ #### Item 13: Avoid Returning or Operating on Unit?
293
+
294
+ **Category:** Readability — Return Types
295
+
296
+ **Core Practice:** `Unit?` has only two values: `Unit` and `null`, making it essentially
297
+ a boolean but far less clear. Avoid functions that return `Unit?` or use `Unit?` in
298
+ conditional logic.
299
+
300
+ **Key Techniques:**
301
+ - If a function can succeed or fail, return `Boolean`, `Result`, or a sealed class
302
+ - Don't chain `?.let { }` on functions returning `Unit?` for conditional execution
303
+ - If you see `Unit?` in your code, refactor to use explicit boolean or sealed type
304
+ - Common source: `map {}` on nullable, where the lambda returns Unit
305
+
306
+ **When to Apply:** Whenever you see `Unit?` appear in types, return values, or conditionals.
307
+
308
+ **Anti-Pattern:** `val result: Unit? = user?.let { save(it) }; if (result != null) { ... }`
309
+
310
+ ---
311
+
312
+ #### Item 14: Specify the Variable Type When It Is Not Clear
313
+
314
+ **Category:** Readability — Type Clarity
315
+
316
+ **Core Practice:** Kotlin's type inference is powerful, but when the type isn't obvious
317
+ from the right-hand side, specify it explicitly for readability.
318
+
319
+ **Key Techniques:**
320
+ - Omit type when obvious: `val name = "Alice"`, `val count = 42`, `val users = listOf<User>()`
321
+ - Specify type when not obvious: `val data: UserProfile = api.fetchData()`, `val result: Map<String, List<Int>> = process(input)`
322
+ - Always specify types for public API members (functions, properties)
323
+ - Be explicit with numeric types when precision matters: `val rate: Double = 0.05`
324
+
325
+ **When to Apply:** Whenever the reader would need to navigate to the function definition
326
+ to understand the type. When in doubt, be explicit.
327
+
328
+ **Anti-Pattern:** `val data = repository.getData()` — what type is `data`?
329
+
330
+ ---
331
+
332
+ #### Item 15: Consider Referencing Receivers Explicitly
333
+
334
+ **Category:** Readability — Scope Clarity
335
+
336
+ **Core Practice:** In nested scope functions or classes with multiple receivers (this, outer
337
+ class, extension receiver), use explicit receiver references (`this@ClassName`,
338
+ labeled `this@functionName`) to avoid ambiguity.
339
+
340
+ **Key Techniques:**
341
+ - In nested lambdas with receivers, use labels: `this@outer` vs `this@inner`
342
+ - In extension functions called within a class, be explicit about which `this` is used
343
+ - Use `@DslMarker` in DSLs to prevent accidental use of outer receivers
344
+ - Prefer `also` (uses `it`) over `apply` (uses `this`) when the receiver context would be confusing
345
+ - When using `with`/`apply`/`run`, ensure the reader can clearly identify the receiver
346
+
347
+ **When to Apply:** Whenever there's ambiguity about which receiver is being used, especially
348
+ in nested scope functions, DSLs, and classes with extension function members.
349
+
350
+ **Anti-Pattern:** Nested `apply` blocks where it's unclear which `this` is being referenced;
351
+ DSL builders that accidentally access outer scope receivers.
352
+
353
+ ---
354
+
355
+ #### Item 16: Properties Should Represent State, Not Behavior
356
+
357
+ **Category:** Readability — Properties vs Functions
358
+
359
+ **Core Practice:** Properties should represent the state of an object. If an operation is
360
+ computationally expensive, has side effects, or conceptually does something rather than
361
+ returns something, use a function instead.
362
+
363
+ **Key Techniques:**
364
+ - Property: `val name: String`, `val isValid: Boolean`, `val size: Int`
365
+ - Function: `fun calculateTotal(): BigDecimal`, `fun findUser(id: String): User?`
366
+ - Properties should be O(1) or at most O(n) with caching expectations
367
+ - Properties should not throw exceptions (except for lazy initialization)
368
+ - Properties should be idempotent — same result on consecutive calls (no side effects)
369
+ - Custom getters are fine for derived state: `val fullName: String get() = "$first $last"`
370
+
371
+ **When to Apply:** For every decision between `val x: T get() = ...` and `fun x(): T`.
372
+
373
+ **Anti-Pattern:** `val users: List<User> get() = database.query("SELECT * FROM users")` —
374
+ this is behavior masquerading as state.
375
+
376
+ ---
377
+
378
+ #### Item 17: Consider Naming Arguments
379
+
380
+ **Category:** Readability — Named Arguments
381
+
382
+ **Core Practice:** Use named arguments for parameters where the meaning isn't clear from
383
+ context, especially for booleans, numbers, strings, and functions with multiple parameters
384
+ of the same type.
385
+
386
+ **Key Techniques:**
387
+ - Named booleans: `setVisible(visible = true)` instead of `setVisible(true)`
388
+ - Named same-type params: `sendMessage(from = alice, to = bob)` instead of `sendMessage(alice, bob)`
389
+ - Named lambda: `repeat(times = 3) { ... }`
390
+ - Always name arguments for builder-style or configuration functions
391
+ - Optional parameters with defaults benefit greatly from naming: `createUser(name = "Alice", role = Admin, active = true)`
392
+ - You don't need to name arguments when meaning is clear from type: `listOf(1, 2, 3)`
393
+
394
+ **When to Apply:** Whenever the argument's purpose isn't obvious from its type and position.
395
+ Especially for boolean parameters, same-typed parameters, and numeric parameters.
396
+
397
+ **Anti-Pattern:** `createRect(0, 0, 100, 50, true, false)` — what do these mean?
398
+
399
+ ---
400
+
401
+ #### Item 18: Respect Coding Conventions
402
+
403
+ **Category:** Readability — Conventions
404
+
405
+ **Core Practice:** Follow Kotlin's official coding conventions and the project's style guide.
406
+ Consistency across a codebase is more important than any individual preference.
407
+
408
+ **Key Techniques:**
409
+ - Follow the Kotlin Coding Conventions (kotlinlang.org/docs/coding-conventions.html)
410
+ - Use ktlint or detekt for automated enforcement
411
+ - CamelCase for classes, camelCase for functions/properties, SCREAMING_SNAKE_CASE for constants
412
+ - Package naming: lowercase, no underscores
413
+ - File naming: PascalCase matching the main class, or descriptive lowercase for utility files
414
+ - Indentation: 4 spaces (Kotlin convention)
415
+ - Blank lines: separate logical sections, between functions, before/after class bodies
416
+
417
+ **When to Apply:** Always. Adopt a style guide and enforce it with tooling.
418
+
419
+ **Anti-Pattern:** Inconsistent naming; mixing conventions from other languages (snake_case
420
+ for functions, Hungarian notation); tabs-vs-spaces debates without tooling enforcement.
421
+
422
+ ---
423
+
424
+ ## Part 2: Code Design
425
+
426
+ ### Chapter 3: Reusability (Items 19–25)
427
+
428
+ #### Item 19: Do Not Repeat Knowledge
429
+
430
+ **Category:** Reusability — DRY Principle
431
+
432
+ **Core Practice:** Every piece of knowledge should have a single, unambiguous representation
433
+ in the system. Duplication leads to inconsistency when one copy is updated but others aren't.
434
+
435
+ **Key Techniques:**
436
+ - Extract common logic into shared functions or classes
437
+ - Distinguish between true knowledge duplication (same business rule expressed twice) and
438
+ accidental similarity (two things that happen to look alike but vary independently)
439
+ - Single source of truth for: business rules, algorithms, validation logic, URL patterns, key names
440
+ - Use constants for magic values, enums for fixed sets, configuration for environment-specific values
441
+ - Don't over-DRY: if two similar-looking code sections serve different concerns and change
442
+ for different reasons, they may be better kept separate
443
+
444
+ **When to Apply:** When the same business rule, algorithm, or decision exists in multiple
445
+ places. NOT when two code sections coincidentally look similar but represent different concepts.
446
+
447
+ **Anti-Pattern:** Same validation logic in UI layer and service layer; hardcoded strings
448
+ duplicated across files; copy-pasted algorithm with slight variations.
449
+
450
+ ---
451
+
452
+ #### Item 20: Do Not Repeat Common Algorithms
453
+
454
+ **Category:** Reusability — Standard Library Usage
455
+
456
+ **Core Practice:** Kotlin's standard library provides a rich set of collection operations,
457
+ scope functions, and utility functions. Use them instead of writing custom implementations.
458
+
459
+ **Key Techniques:**
460
+ - Collection: `map`, `filter`, `flatMap`, `fold`, `reduce`, `groupBy`, `associate`, `partition`, `zip`, `windowed`, `chunked`
461
+ - Searching: `find`, `first`, `firstOrNull`, `any`, `all`, `none`, `count`
462
+ - Transformation: `sorted`, `sortedBy`, `distinct`, `distinctBy`, `take`, `drop`, `reversed`
463
+ - Aggregation: `sum`, `sumOf`, `average`, `min`, `max`, `minBy`, `maxBy`
464
+ - Scope: `let`, `run`, `with`, `apply`, `also`
465
+ - String: `buildString`, `joinToString`, `split`, `replace`, `trim`, `padStart`, `padEnd`
466
+ - If a common algorithm isn't in stdlib, extract it as an extension function in your project
467
+
468
+ **When to Apply:** Before writing any collection processing, searching, sorting, or
469
+ string manipulation logic, check if a stdlib function already does it.
470
+
471
+ **Anti-Pattern:** Writing manual loops for filtering, manual StringBuilder usage instead
472
+ of `buildString`/`joinToString`, reimplementing `groupBy` or `associate`.
473
+
474
+ ---
475
+
476
+ #### Item 21: Use Property Delegation to Extract Common Property Patterns
477
+
478
+ **Category:** Reusability — Delegation
479
+
480
+ **Core Practice:** Kotlin's property delegation (`by` keyword) lets you extract common
481
+ property patterns like lazy initialization, observable changes, and map-backed properties.
482
+
483
+ **Key Techniques:**
484
+ - `by lazy { }` — lazy initialization (thread-safe by default)
485
+ - `by Delegates.observable(initial) { prop, old, new -> }` — react to changes
486
+ - `by Delegates.vetoable(initial) { prop, old, new -> condition }` — validate changes
487
+ - `by map` — delegate to a map for dynamic property lookup
488
+ - Custom delegates: implement `ReadOnlyProperty` or `ReadWriteProperty`
489
+ - Use custom delegates for cross-cutting concerns: logging, validation, caching, preferences
490
+
491
+ **When to Apply:** When you see repeated property patterns (lazy init, change notification,
492
+ validation on set, caching). Extract the pattern into a delegate.
493
+
494
+ **Anti-Pattern:** Manual lazy initialization with null checks; manual change listeners with
495
+ backing fields; repeated boilerplate property patterns across classes.
496
+
497
+ ---
498
+
499
+ #### Item 22: Use Generics When Implementing Common Algorithms
500
+
501
+ **Category:** Reusability — Generics
502
+
503
+ **Core Practice:** When extracting common algorithms, use generics to make them work
504
+ with any type rather than duplicating for specific types.
505
+
506
+ **Key Techniques:**
507
+ - Generic functions: `fun <T> List<T>.lastHalf(): List<T>`
508
+ - Generic classes: `class Cache<T>(val loader: () -> T)`
509
+ - Bounded generics: `fun <T : Comparable<T>> List<T>.sorted(): List<T>`
510
+ - Multiple bounds: `fun <T> doWork(value: T) where T : Serializable, T : Comparable<T>`
511
+ - Use generics with extension functions for powerful, reusable utilities
512
+ - Star projection (`*`) when you don't care about the type parameter
513
+
514
+ **When to Apply:** When writing utility functions or classes that operate on "some type"
515
+ where the algorithm doesn't depend on the specific type.
516
+
517
+ **Anti-Pattern:** Separate `processStringList()`, `processIntList()`, `processUserList()`
518
+ functions that do the same thing with different types.
519
+
520
+ ---
521
+
522
+ #### Item 23: Avoid Shadowing Type Parameters
523
+
524
+ **Category:** Reusability — Generic Safety
525
+
526
+ **Core Practice:** Don't declare a type parameter on a function that shadows a type parameter
527
+ from the enclosing class. This creates confusing independent type parameters that look related.
528
+
529
+ **Key Techniques:**
530
+ - If a class is `class Box<T>`, a member function using `T` should NOT redeclare `<T>`
531
+ - Use different names if you need an independent type parameter: `fun <R> map(f: (T) -> R): Box<R>`
532
+ - Shadowing leads to subtle bugs where the function's `T` is unrelated to the class's `T`
533
+
534
+ **When to Apply:** When writing generic member functions in generic classes.
535
+
536
+ **Anti-Pattern:** `class Box<T> { fun <T> add(item: T) }` — the function's `T` shadows and
537
+ is independent from the class's `T`.
538
+
539
+ ---
540
+
541
+ #### Item 24: Consider Variance for Generic Types
542
+
543
+ **Category:** Reusability — Variance
544
+
545
+ **Core Practice:** Use declaration-site variance (`in`/`out` modifiers) to specify whether a
546
+ generic type is a producer (covariant, `out`) or consumer (contravariant, `in`), enabling
547
+ more flexible subtype relationships.
548
+
549
+ **Key Techniques:**
550
+ - `out T` (covariant): the class only produces T (returns T, never takes T as input). `List<out T>` means `List<Dog>` is a subtype of `List<Animal>`
551
+ - `in T` (contravariant): the class only consumes T (takes T as input, never returns T). `Comparable<in T>` means `Comparable<Animal>` is a subtype of `Comparable<Dog>`
552
+ - Use-site variance (`out`/`in` at call site) when declaration-site isn't possible
553
+ - Star projection (`*`) when you don't care about the type parameter at all
554
+ - Kotlin collections use this well: `List<out T>` is covariant, `MutableList<T>` is invariant
555
+
556
+ **When to Apply:** When designing generic interfaces and classes. Think about whether your
557
+ type only produces, only consumes, or does both.
558
+
559
+ **Anti-Pattern:** Invariant type parameters where covariance would be safe and useful;
560
+ requiring exact type matches where subtype relationships should work.
561
+
562
+ ---
563
+
564
+ #### Item 25: Reuse Between Different Platforms
565
+
566
+ **Category:** Reusability — Multiplatform
567
+
568
+ **Core Practice:** Kotlin Multiplatform allows sharing code between JVM, JS, Native, and
569
+ other targets. Put platform-independent logic in common modules.
570
+
571
+ **Key Techniques:**
572
+ - `expect`/`actual` mechanism for platform-specific implementations
573
+ - Common module for: business logic, data models, validation, algorithms
574
+ - Platform modules for: UI, platform APIs, file system, networking specifics
575
+ - Share serialization models with kotlinx.serialization
576
+ - Share coroutine-based async code with kotlinx.coroutines
577
+ - Use `expect`/`actual` sparingly — prefer interfaces with platform-specific DI
578
+
579
+ **When to Apply:** When building applications that target multiple platforms (mobile,
580
+ web, server). Put as much business logic as possible in common code.
581
+
582
+ **Anti-Pattern:** Duplicating business logic per platform; using platform-specific APIs
583
+ in code that could be platform-independent.
584
+
585
+ ---
586
+
587
+ ### Chapter 4: Abstraction Design (Items 26–32)
588
+
589
+ #### Item 26: Each Function Should Be Written in Terms of a Single Level of Abstraction
590
+
591
+ **Category:** Abstraction — Function Design
592
+
593
+ **Core Practice:** A function should operate at one consistent level of abstraction. Don't
594
+ mix high-level operations (business logic) with low-level details (string parsing, I/O).
595
+
596
+ **Key Techniques:**
597
+ - Extract low-level details into well-named helper functions
598
+ - A high-level function should read like a summary of steps: `validate()`, `process()`, `save()`
599
+ - Each helper function should also maintain a single abstraction level
600
+ - This creates a natural hierarchy: high-level orchestration → mid-level operations → low-level details
601
+ - Functions at the same level of abstraction should have similar levels of detail in their names
602
+
603
+ **When to Apply:** When a function mixes different levels of detail. The classic sign is
604
+ a function that alternates between business-meaningful operations and mechanical details.
605
+
606
+ **Anti-Pattern:** A function that calls `validateOrder(order)` then directly manipulates
607
+ `StringBuilder` for formatting, then calls `repository.save()`.
608
+
609
+ ---
610
+
611
+ #### Item 27: Use Abstraction to Protect Code Against Changes
612
+
613
+ **Category:** Abstraction — Change Protection
614
+
615
+ **Core Practice:** Introduce abstractions (interfaces, abstract classes, function types)
616
+ at boundaries where change is expected. Abstractions isolate the impact of changes.
617
+
618
+ **Key Techniques:**
619
+ - Interface for dependencies that might have multiple implementations (repositories, services)
620
+ - Function type parameters for varying behavior (`(T) -> R` instead of a specific strategy class)
621
+ - Wrapper/adapter classes around external APIs that might change
622
+ - Constant extraction: name magic values, extract configuration
623
+ - Use abstraction layers at system boundaries: database, network, file system, third-party APIs
624
+
625
+ **When to Apply:** At boundaries where the other side might change: external APIs,
626
+ infrastructure dependencies, business rules that vary by configuration.
627
+
628
+ **Anti-Pattern:** Directly depending on a specific HTTP library throughout the codebase;
629
+ hardcoding database queries in business logic; no abstraction around third-party SDKs.
630
+
631
+ ---
632
+
633
+ #### Item 28: Specify API Stability
634
+
635
+ **Category:** Abstraction — API Lifecycle
636
+
637
+ **Core Practice:** Communicate the stability of your API elements so consumers know what's
638
+ safe to depend on. Use annotations and versioning conventions.
639
+
640
+ **Key Techniques:**
641
+ - `@Deprecated("message", replaceWith = ReplaceWith("newFunction()"))` — for removed APIs
642
+ - `@OptIn(ExperimentalApi::class)` / `@RequiresOptIn` — for experimental/unstable APIs
643
+ - Semantic versioning: major for breaking changes, minor for additions, patch for fixes
644
+ - Mark internal implementation details as `internal` or `private`
645
+ - Document stability guarantees in KDoc
646
+ - Use `@Deprecated` with `DeprecationLevel.WARNING`, then `ERROR`, then `HIDDEN` for gradual removal
647
+
648
+ **When to Apply:** For any code consumed by other modules or teams. Library authors
649
+ must be especially careful about API stability contracts.
650
+
651
+ **Anti-Pattern:** Breaking public API without deprecation cycle; experimental APIs without
652
+ opt-in annotation; no versioning strategy.
653
+
654
+ ---
655
+
656
+ #### Item 29: Consider Wrapping External APIs
657
+
658
+ **Category:** Abstraction — Dependency Isolation
659
+
660
+ **Core Practice:** Wrap third-party libraries and external APIs behind your own interfaces
661
+ to isolate your code from their changes, enable testing, and allow swapping implementations.
662
+
663
+ **Key Techniques:**
664
+ - Create a repository/service interface for each external dependency
665
+ - Implement the interface using the specific library
666
+ - Inject the interface, not the implementation
667
+ - This enables: unit testing with fakes, swapping implementations, adapting to API changes
668
+ - Don't over-wrap: simple, stable utilities (like Kotlin stdlib) don't need wrapping
669
+ - Focus wrapping effort on: HTTP clients, databases, third-party SDKs, analytics, logging
670
+
671
+ **When to Apply:** For external dependencies that are complex, likely to change, or need
672
+ to be mocked in tests.
673
+
674
+ **Anti-Pattern:** Calling Retrofit/OkHttp/Room directly throughout business logic; untestable
675
+ code due to tight coupling with external libraries.
676
+
677
+ ---
678
+
679
+ #### Item 30: Minimize Elements' Visibility
680
+
681
+ **Category:** Abstraction — Encapsulation
682
+
683
+ **Core Practice:** Use the most restrictive visibility modifier that works. Less visibility
684
+ means less API surface, fewer invariants to maintain, and more freedom to refactor.
685
+
686
+ **Key Techniques:**
687
+ - Default to `private`; widen only when needed
688
+ - Kotlin visibility: `private` (file/class), `protected` (class + subclasses), `internal` (module), `public` (everyone)
689
+ - Use `internal` for module-internal APIs that shouldn't be exposed to consumers
690
+ - Properties: prefer `private set` with public getter if external mutation isn't needed
691
+ - Classes: prefer `private` nested classes or `internal` top-level classes
692
+ - In interfaces, keep the surface minimal — don't expose implementation helpers
693
+
694
+ **When to Apply:** For every class, function, and property declaration. Start `private`
695
+ and widen only with justification.
696
+
697
+ **Anti-Pattern:** Everything `public` by default; `internal` classes with `public` members
698
+ that expose implementation; mutable properties without restricted setters.
699
+
700
+ ---
701
+
702
+ #### Item 31: Define Contract with Documentation
703
+
704
+ **Category:** Abstraction — Documentation
705
+
706
+ **Core Practice:** Use KDoc to document the contract of public API elements: what the function
707
+ does, what it expects, what it returns, and what exceptions it may throw.
708
+
709
+ **Key Techniques:**
710
+ - `/** */` KDoc for all public classes, functions, and properties
711
+ - `@param` for parameter descriptions
712
+ - `@return` for return value description
713
+ - `@throws` / `@exception` for documented exceptions
714
+ - `@sample` for linking to example code
715
+ - `@see` for referencing related elements
716
+ - Focus documentation on the *what* and *why*, not the *how* (code shows how)
717
+ - Document edge cases, nullability semantics, threading guarantees, and lifecycle
718
+
719
+ **When to Apply:** For all public API elements. Internal elements benefit from documentation
720
+ when the behavior isn't obvious from the name and signature.
721
+
722
+ **Anti-Pattern:** No KDoc on public APIs; documentation that restates the function name
723
+ (`/** Gets the user */` on `fun getUser()`); outdated documentation.
724
+
725
+ ---
726
+
727
+ #### Item 32: Respect Abstraction Contracts
728
+
729
+ **Category:** Abstraction — Contract Compliance
730
+
731
+ **Core Practice:** When implementing an interface or extending a class, respect the contract
732
+ defined by the supertype. Violating contracts leads to bugs in code that depends on the
733
+ documented behavior (Liskov Substitution Principle).
734
+
735
+ **Key Techniques:**
736
+ - Read and follow the documented contracts of interfaces you implement
737
+ - Maintain postconditions: if the contract says "returns non-empty list," ensure it
738
+ - Maintain invariants: if the contract says "thread-safe," ensure thread safety
739
+ - `equals`, `hashCode`, `compareTo`, `toString` all have well-defined contracts — respect them
740
+ - When overriding, the subclass behavior should be substitutable for the superclass
741
+ - Don't throw unexpected exceptions from overridden methods
742
+
743
+ **When to Apply:** Whenever implementing interfaces or extending classes, especially
744
+ well-known contracts like `Collection`, `Comparable`, `Iterable`.
745
+
746
+ **Anti-Pattern:** `equals` that isn't symmetric; `Comparable` that isn't consistent with
747
+ `equals`; `Iterator` that doesn't throw `NoSuchElementException` when exhausted.
748
+
749
+ ---
750
+
751
+ ### Chapter 5: Object Creation (Items 33–35)
752
+
753
+ #### Item 33: Consider Factory Functions Instead of Constructors
754
+
755
+ **Category:** Object Creation — Factory Patterns
756
+
757
+ **Core Practice:** Factory functions offer naming, caching, return-type flexibility,
758
+ and can return subtypes. Use them when constructors are limiting.
759
+
760
+ **Key Techniques:**
761
+ - Companion object factory: `User.create(name)` or `User(name)` (invoke operator)
762
+ - Top-level factory: `listOf()`, `mapOf()`, `buildString { }`
763
+ - Extension factory: `String.toUser()` — conversion factory as extension
764
+ - Fake constructors: companion `invoke` operator for constructor-like syntax with factory benefits
765
+ - Factory functions can: have descriptive names, cache instances, return subtypes, hide implementation
766
+ - Kotlin stdlib examples: `listOf()`, `mutableListOf()`, `lazy { }`, `coroutineScope { }`
767
+
768
+ **When to Apply:** When you need: named construction, caching/pooling, subtype returns,
769
+ complex initialization logic, or platform factory patterns (Android `Fragment.newInstance()`).
770
+
771
+ **Anti-Pattern:** Complex logic in constructors; constructors that do I/O or heavy
772
+ computation; inability to name construction scenarios; no caching opportunity.
773
+
774
+ ---
775
+
776
+ #### Item 34: Consider a Primary Constructor with Named Optional Arguments
777
+
778
+ **Category:** Object Creation — Constructor Design
779
+
780
+ **Core Practice:** Kotlin's primary constructor with default parameter values and named
781
+ arguments often eliminates the need for the Builder pattern, telescoping constructors,
782
+ or multiple factory overloads.
783
+
784
+ **Key Techniques:**
785
+ - Primary constructor with defaults: `class User(val name: String, val age: Int = 0, val active: Boolean = true)`
786
+ - Callers use named args: `User(name = "Alice", active = false)`
787
+ - This gives the builder pattern's readability without the ceremony
788
+ - Migration: convert Java builders to Kotlin primary constructors with defaults
789
+ - Data classes work perfectly with this pattern: `data class Config(val host: String = "localhost", val port: Int = 8080)`
790
+ - Use `copy()` on data classes for partial modifications
791
+
792
+ **When to Apply:** As the default object creation approach in Kotlin. Prefer over
793
+ Builder pattern unless construction involves complex validation or multi-step building.
794
+
795
+ **Anti-Pattern:** Java-style Builder pattern in Kotlin (rarely needed); telescoping
796
+ constructor overloads; multiple factory functions that differ only in defaults.
797
+
798
+ ---
799
+
800
+ #### Item 35: Consider Defining a DSL for Complex Object Creation
801
+
802
+ **Category:** Object Creation — DSLs
803
+
804
+ **Core Practice:** For complex, hierarchical object creation, Kotlin's type-safe builders
805
+ (DSLs with lambda receivers) provide the most readable syntax.
806
+
807
+ **Key Techniques:**
808
+ - Lambda with receiver: `fun html(init: HTML.() -> Unit): HTML`
809
+ - `@DslMarker` to restrict scope and prevent accidental use of outer receivers
810
+ - `inline` builder functions to eliminate lambda overhead
811
+ - Builder DSL examples: HTML builders, Gradle build scripts, Ktor routing, kotlinx.html
812
+ - Use when: object hierarchies, configuration, structured data, multi-step assembly
813
+ - Combine with `apply`: `val config = Config().apply { host = "localhost"; port = 8080 }`
814
+
815
+ **When to Apply:** When building hierarchical or tree-structured objects (UI layouts,
816
+ configuration, document structures, routing tables).
817
+
818
+ **Anti-Pattern:** Nested constructor calls for hierarchical structures; verbose imperative
819
+ code for what is naturally a declarative structure.
820
+
821
+ ---
822
+
823
+ ### Chapter 6: Class Design (Items 36–44)
824
+
825
+ #### Item 36: Prefer Composition over Inheritance
826
+
827
+ **Category:** Class Design — Composition vs Inheritance
828
+
829
+ **Core Practice:** Use composition (HAS-A) with delegation over inheritance (IS-A) when
830
+ the goal is code reuse. Inheritance creates tight coupling and fragile hierarchies.
831
+
832
+ **Key Techniques:**
833
+ - Kotlin's `by` delegation: `class CountingSet<T>(val inner: MutableSet<T> = mutableSetOf()) : MutableSet<T> by inner`
834
+ - Interface delegation eliminates boilerplate forwarding methods
835
+ - Use inheritance only for true IS-A relationships with substitutability
836
+ - Prefer interfaces over abstract classes for defining capabilities
837
+ - Composition allows: changing implementation at runtime, combining multiple behaviors, easier testing
838
+
839
+ **When to Apply:** Default to composition. Use inheritance only when there's a genuine
840
+ IS-A relationship and the Liskov Substitution Principle is satisfied.
841
+
842
+ **Anti-Pattern:** Inheriting from a utility class for code reuse; deep inheritance trees;
843
+ overriding methods in ways that break superclass assumptions; "inheritance for convenience."
844
+
845
+ ---
846
+
847
+ #### Item 37: Use the Data Modifier to Represent a Bundle of Data
848
+
849
+ **Category:** Class Design — Data Classes
850
+
851
+ **Core Practice:** Use `data class` for classes whose primary purpose is holding data.
852
+ They automatically generate `equals`, `hashCode`, `toString`, `copy`, and destructuring.
853
+
854
+ **Key Techniques:**
855
+ - `data class User(val name: String, val email: String)` — auto-generated methods
856
+ - `copy()` for creating modified copies: `user.copy(name = "New Name")`
857
+ - Destructuring: `val (name, email) = user`
858
+ - Properties in the primary constructor participate in equals/hashCode/toString/copy
859
+ - Properties declared in the body do NOT participate in generated methods
860
+ - Use data classes for: DTOs, value objects, events, messages, records
861
+ - Data classes should generally be immutable (all `val` properties)
862
+
863
+ **When to Apply:** For any class that primarily holds data and needs structural equality,
864
+ copying, and sensible toString.
865
+
866
+ **Anti-Pattern:** Regular classes with manually written equals/hashCode/toString for data
867
+ holders; mutable data classes with var properties (breaks hash-based collections).
868
+
869
+ ---
870
+
871
+ #### Item 38: Use Function Types or Functional Interfaces to Pass Behaviors
872
+
873
+ **Category:** Class Design — Behavioral Abstraction
874
+
875
+ **Core Practice:** Instead of interfaces with a single method (SAM interfaces in Java), use
876
+ Kotlin function types `(A) -> B` or `fun interface` for passing behavior.
877
+
878
+ **Key Techniques:**
879
+ - Function type parameter: `fun process(filter: (User) -> Boolean)`
880
+ - Type alias for clarity: `typealias UserFilter = (User) -> Boolean`
881
+ - `fun interface` (SAM) when you want both lambda convenience and named interface: `fun interface Validator { fun validate(input: String): Boolean }`
882
+ - Lambda expressions are naturally concise: `process { it.isActive }`
883
+ - Method references for existing functions: `process(User::isActive)`
884
+ - `fun interface` gives SAM conversion from Java and named type for documentation
885
+
886
+ **When to Apply:** When a behavior needs to be parameterized. Use function types for
887
+ simple cases; `fun interface` when you need a named concept with documentation.
888
+
889
+ **Anti-Pattern:** Regular interfaces with a single method that require anonymous object
890
+ syntax; verbose callback interfaces where a lambda would suffice.
891
+
892
+ ---
893
+
894
+ #### Item 39: Prefer Class Hierarchies Instead of Tagged Classes
895
+
896
+ **Category:** Class Design — Sealed Hierarchies
897
+
898
+ **Core Practice:** Replace "tagged classes" (classes with a type enum and conditional logic)
899
+ with sealed class hierarchies. Each variant becomes its own class with type-specific behavior.
900
+
901
+ **Key Techniques:**
902
+ - Sealed class/interface: `sealed interface Shape { data class Circle(val r: Double) : Shape; data class Rect(val w: Double, val h: Double) : Shape }`
903
+ - `when` exhaustiveness: compiler ensures all cases are handled
904
+ - Each subclass carries only the data it needs (no null fields for other variants)
905
+ - Type-specific behavior lives in the subclass, not in switch/when blocks
906
+ - Sealed interfaces allow a class to implement multiple sealed hierarchies
907
+
908
+ **When to Apply:** Whenever you see a class with: a type/kind enum field, when/switch
909
+ blocks checking that type, nullable fields that are only relevant for some types.
910
+
911
+ **Anti-Pattern:** `class Shape(val type: Type, val radius: Double?, val width: Double?, val height: Double?)` with `when (type)` checks everywhere.
912
+
913
+ ---
914
+
915
+ #### Item 40: Respect the Contract of equals
916
+
917
+ **Category:** Class Design — Equality
918
+
919
+ **Core Practice:** `equals` must satisfy: reflexive (a == a), symmetric (a == b ↔ b == a),
920
+ transitive (a == b && b == c → a == c), consistent (same result on repeated calls),
921
+ and null comparison (a.equals(null) == false).
922
+
923
+ **Key Techniques:**
924
+ - Use `data class` for automatic correct `equals` (based on constructor properties)
925
+ - For custom `equals`: override both `equals` and `hashCode` together
926
+ - `equals` should compare by value, not identity, for value types
927
+ - Be careful with inheritance: a subclass `equals` can easily break symmetry with parent
928
+ - Consider using `abstract class` with `equals` defined in terms of an abstract property set
929
+ - Use sealed classes to avoid the inheritance-equality problem
930
+
931
+ **When to Apply:** When defining value types or any class used in collections, maps,
932
+ or equality comparisons. `data class` handles most cases automatically.
933
+
934
+ **Anti-Pattern:** Overriding `equals` without `hashCode`; `equals` that isn't symmetric
935
+ between parent and child; mutable properties in `equals` computation.
936
+
937
+ ---
938
+
939
+ #### Item 41: Respect the Contract of hashCode
940
+
941
+ **Category:** Class Design — Hashing
942
+
943
+ **Core Practice:** `hashCode` must be consistent with `equals`: if `a == b`, then
944
+ `a.hashCode() == b.hashCode()`. Objects used as hash map keys or set elements MUST
945
+ have correct hashCode.
946
+
947
+ **Key Techniques:**
948
+ - `data class` generates correct `hashCode` automatically
949
+ - Always override `hashCode` when overriding `equals`
950
+ - Use the same properties in `hashCode` that you use in `equals`
951
+ - Kotlin's `Objects.hash()` or manual combination: `31 * hash1 + hash2`
952
+ - Immutable objects: compute hash once and cache it
953
+ - Mutable objects used as keys is dangerous: mutation changes hash, losing the entry
954
+
955
+ **When to Apply:** Whenever overriding `equals`. Also when placing objects in hash-based
956
+ collections (HashMap, HashSet, LinkedHashMap).
957
+
958
+ **Anti-Pattern:** `equals` override without `hashCode` override; mutable properties in
959
+ hashCode; hashCode that returns a constant (legal but destroys performance).
960
+
961
+ ---
962
+
963
+ #### Item 42: Respect the Contract of compareTo
964
+
965
+ **Category:** Class Design — Ordering
966
+
967
+ **Core Practice:** `compareTo` (and `Comparable` interface) must be: antisymmetric
968
+ (a > b implies b < a), transitive (a > b && b > c implies a > c), and consistent
969
+ (`a.compareTo(b) == 0` should ideally mean `a == b`).
970
+
971
+ **Key Techniques:**
972
+ - Implement `Comparable<T>` for natural ordering: `class User : Comparable<User>`
973
+ - Use `compareBy`/`compareByDescending` for clean comparator construction
974
+ - `compareValuesBy(this, other, { it.lastName }, { it.firstName })` for multi-field comparison
975
+ - Sorted collections, ranges, and sorting functions all rely on correct compareTo
976
+ - Ensure consistency with equals when possible (TreeMap depends on this)
977
+
978
+ **When to Apply:** When objects have a natural ordering. Use `Comparator` for alternative
979
+ orderings that don't change the class definition.
980
+
981
+ **Anti-Pattern:** `compareTo` that returns 0 for unequal objects used in TreeSet (loses entries);
982
+ inconsistent ordering that isn't transitive.
983
+
984
+ ---
985
+
986
+ #### Item 43: Consider Extracting Non-Essential Parts of Your API into Extensions
987
+
988
+ **Category:** Class Design — Extension Functions
989
+
990
+ **Core Practice:** Keep classes focused on their core responsibility. Move non-essential
991
+ operations (convenience methods, formatting, integration utilities) to extension functions.
992
+
993
+ **Key Techniques:**
994
+ - Core API in the class: essential operations that need access to private state
995
+ - Extensions for: convenience overloads, format conversions, integration with other libraries
996
+ - Extensions can be imported selectively — unlike members which are always available
997
+ - Extensions improve discoverability through IDE completion based on receiver type
998
+ - Extension properties for derived state that doesn't need private access:
999
+ `val String.isPalindrome: Boolean get() = this == reversed()`
1000
+
1001
+ **When to Apply:** When a method doesn't need private access and represents a secondary
1002
+ operation. When different consumers need different utility functions on the same type.
1003
+
1004
+ **Anti-Pattern:** Bloated classes with dozens of utility methods; convenience methods
1005
+ that clutter the core API; methods that could work with only the public interface.
1006
+
1007
+ ---
1008
+
1009
+ #### Item 44: Avoid Member Extensions
1010
+
1011
+ **Category:** Class Design — Extension Placement
1012
+
1013
+ **Core Practice:** Extensions defined as members of another class have confusing dispatch
1014
+ behavior (static for the extension receiver, virtual for the dispatch receiver). Prefer
1015
+ top-level extensions or local extensions.
1016
+
1017
+ **Key Techniques:**
1018
+ - Top-level extensions: `fun String.toCamelCase(): String` — clear and predictable
1019
+ - Local extensions (inside a function): limited scope, good for one-off utilities
1020
+ - Member extensions have two receivers (`this` and the extension receiver), creating confusion
1021
+ - Member extensions can't be used outside the class they're defined in
1022
+ - Only valid use: DSL markers where restricting scope is intentional
1023
+
1024
+ **When to Apply:** Avoid member extensions in general code. Use top-level extensions for
1025
+ broad utilities, local extensions for scoped utilities.
1026
+
1027
+ **Anti-Pattern:** Defining extension functions as members of a class to "organize" them;
1028
+ using member extensions when a top-level extension or regular method would be clearer.
1029
+
1030
+ ---
1031
+
1032
+ ## Part 3: Efficiency
1033
+
1034
+ ### Chapter 7: Make It Cheap (Items 45–48)
1035
+
1036
+ #### Item 45: Avoid Unnecessary Object Creation
1037
+
1038
+ **Category:** Efficiency — Allocation
1039
+
1040
+ **Core Practice:** Object creation has a cost (allocation, initialization, GC pressure).
1041
+ In performance-sensitive code, reuse objects, use primitives, and avoid unnecessary allocation.
1042
+
1043
+ **Key Techniques:**
1044
+ - Object reuse: `companion object` singletons, cached instances, `object` declarations
1045
+ - Use `Int`, `Long`, `Double` (JVM primitives) instead of nullable `Int?`, `Long?`, `Double?` (boxed) in hot paths
1046
+ - String: avoid concatenation in loops, use `buildString` or `StringBuilder`
1047
+ - Caching: `lazy`, companion object caches, `HashMap`-based memoization
1048
+ - Avoid: creating objects in tight loops when a mutable reusable object works
1049
+ - Kotlin-specific: `inline` classes to wrap without allocation; `Sequence` to avoid intermediate lists
1050
+ - Known costly operations: `Regex` creation (compile once, reuse), date formatter creation
1051
+
1052
+ **When to Apply:** In performance-critical paths: tight loops, hot functions, frequently
1053
+ called code. Don't over-optimize cold paths — readability matters more there.
1054
+
1055
+ **Anti-Pattern:** Creating `Regex` in every function call; string concatenation in loops;
1056
+ nullable primitives where non-null works; creating temporary objects per iteration.
1057
+
1058
+ ---
1059
+
1060
+ #### Item 46: Use Inline Modifier for Functions with Functional Parameters
1061
+
1062
+ **Category:** Efficiency — Inline Functions
1063
+
1064
+ **Core Practice:** Lambda parameters create anonymous class instances and add invocation
1065
+ overhead. The `inline` modifier copies the lambda body at the call site, eliminating
1066
+ this overhead.
1067
+
1068
+ **Key Techniques:**
1069
+ - `inline fun <T> Iterable<T>.myFilter(predicate: (T) -> Boolean): List<T>` — no lambda object created
1070
+ - Most stdlib collection functions (`map`, `filter`, `forEach`, `let`, `apply`, etc.) are already inline
1071
+ - `noinline` for lambda parameters that shouldn't be inlined (stored, passed elsewhere)
1072
+ - `crossinline` for lambdas used in other contexts (coroutines, nested lambdas)
1073
+ - `reified` type parameters: only work with `inline` — allow `T::class` usage
1074
+ - Inline functions support non-local returns from lambdas
1075
+
1076
+ **When to Apply:** For higher-order functions that are called frequently, especially
1077
+ utility functions and collection operations. Don't inline large function bodies (code bloat).
1078
+
1079
+ **Anti-Pattern:** Not inlining frequently-called higher-order functions; inlining large
1080
+ functions with minimal lambda parameters (code bloat for little benefit).
1081
+
1082
+ ---
1083
+
1084
+ #### Item 47: Consider Using Inline Value Classes
1085
+
1086
+ **Category:** Efficiency — Value Classes
1087
+
1088
+ **Core Practice:** Inline value classes (`@JvmInline value class`) wrap a single value
1089
+ with type safety but are erased to the underlying type at runtime — zero allocation overhead.
1090
+
1091
+ **Key Techniques:**
1092
+ - `@JvmInline value class UserId(val id: Long)` — type-safe wrapper, compiled to `Long` at runtime
1093
+ - Use for: IDs, quantities with units, validated strings, domain primitives
1094
+ - Prevents mixing up parameters of the same type: `fun assign(userId: UserId, taskId: TaskId)`
1095
+ - Can have methods, implement interfaces, and have init blocks
1096
+ - Limitations: single property in constructor, no `lateinit` or delegated properties
1097
+ - On JVM, boxing occurs when: used as nullable, used as generic type parameter, used through interface
1098
+
1099
+ **When to Apply:** When you want type safety for primitive-like values without the
1100
+ overhead of full wrapper classes. Great for domain IDs, measurements, and validated wrappers.
1101
+
1102
+ **Anti-Pattern:** Using raw `Long` for user IDs, task IDs, and order IDs (easy to mix up);
1103
+ using full classes for simple wrappers adding GC pressure in hot paths.
1104
+
1105
+ ---
1106
+
1107
+ #### Item 48: Eliminate Obsolete Object References
1108
+
1109
+ **Category:** Efficiency — Memory Management
1110
+
1111
+ **Core Practice:** Even with garbage collection, memory leaks occur when objects hold
1112
+ references to other objects that are no longer needed. Null out references, use weak
1113
+ references, and clean up listeners/callbacks.
1114
+
1115
+ **Key Techniques:**
1116
+ - Set fields to `null` when the referenced object is no longer needed
1117
+ - Clear collections that hold references to expired data
1118
+ - Use `WeakReference` for caches and observer registrations
1119
+ - Android-specific: unregister listeners in `onDestroy`/`onStop`, avoid Activity references in long-lived objects
1120
+ - Beware closures: lambdas capture outer variables, potentially keeping large objects alive
1121
+ - Use memory profilers to detect leaks: Android Studio Profiler, VisualVM, YourKit
1122
+ - Common sources: static collections, listeners/callbacks, caches without eviction
1123
+
1124
+ **When to Apply:** Especially in long-running applications (servers, Android apps) and
1125
+ when dealing with callbacks, caches, and observer patterns.
1126
+
1127
+ **Anti-Pattern:** Growing static collections without cleanup; registered observers never
1128
+ unregistered; closures capturing Activity/Fragment references on Android.
1129
+
1130
+ ---
1131
+
1132
+ ### Chapter 8: Efficient Collection Processing (Items 49–52)
1133
+
1134
+ #### Item 49: Prefer Sequence for Big Collections with More Than One Processing Step
1135
+
1136
+ **Category:** Efficiency — Lazy Processing
1137
+
1138
+ **Core Practice:** Standard collection operations (`map`, `filter`, etc.) are eager — each
1139
+ step creates an intermediate collection. `Sequence` is lazy — elements are processed one
1140
+ at a time through the entire pipeline, with no intermediate collections.
1141
+
1142
+ **Key Techniques:**
1143
+ - Convert with `.asSequence()`: `list.asSequence().filter { ... }.map { ... }.toList()`
1144
+ - Sequence advantages: no intermediate collections, short-circuits (find/first/any stop early), handles large/infinite data
1145
+ - Collection advantages: simpler debugging, order of operations sometimes matters, result is ready to use
1146
+ - Rule of thumb: use Sequence when you have multiple chained operations on a large collection
1147
+ - Terminal operations (`.toList()`, `.first()`, `.count()`, `.forEach()`) trigger evaluation
1148
+ - `sequence { yield(); yieldAll() }` for custom generators
1149
+ - For file processing: `File.useLines()` returns a Sequence
1150
+
1151
+ **When to Apply:** When chaining 2+ operations on collections of hundreds+ elements. The
1152
+ larger the collection and the more operations, the bigger the benefit.
1153
+
1154
+ **Anti-Pattern:** Chaining `filter { }.map { }.flatMap { }` on large lists (creates 3 intermediate lists); not using `asSequence()` before complex collection pipelines.
1155
+
1156
+ ---
1157
+
1158
+ #### Item 50: Limit the Number of Operations
1159
+
1160
+ **Category:** Efficiency — Operation Optimization
1161
+
1162
+ **Core Practice:** Choose the right collection function to minimize the number of operations.
1163
+ Many common patterns have single-function equivalents that avoid unnecessary work.
1164
+
1165
+ **Key Techniques:**
1166
+ - `any { }` instead of `filter { }.isNotEmpty()` — short-circuits on first match
1167
+ - `none { }` instead of `filter { }.isEmpty()` — short-circuits on first match
1168
+ - `firstOrNull { }` instead of `filter { }.firstOrNull()` — stops at first match
1169
+ - `count { }` instead of `filter { }.count()` — avoids intermediate list
1170
+ - `mapNotNull { }` instead of `map { }.filterNotNull()` — single pass
1171
+ - `maxByOrNull { }` instead of `sortedBy { }.last()` — O(n) instead of O(n log n)
1172
+ - `associateBy { }` instead of `map { it.key to it }.toMap()` — direct construction
1173
+ - `flatMap { }` instead of `map { }.flatten()` — single pass
1174
+ - `sumOf { }` instead of `map { }.sum()` — avoids intermediate list
1175
+
1176
+ **When to Apply:** Every time you chain collection operations. Check if a single function
1177
+ can replace two chained ones.
1178
+
1179
+ **Anti-Pattern:** `list.filter { it > 0 }.count()` instead of `list.count { it > 0 }`;
1180
+ `list.sortedBy { it.name }.first()` instead of `list.minByOrNull { it.name }`.
1181
+
1182
+ ---
1183
+
1184
+ #### Item 51: Consider Arrays with Primitives for Performance-Critical Processing
1185
+
1186
+ **Category:** Efficiency — Primitive Arrays
1187
+
1188
+ **Core Practice:** `Array<Int>` boxes each element as `Integer`. For performance-critical
1189
+ numeric processing, use `IntArray`, `LongArray`, `DoubleArray`, etc. which map to JVM
1190
+ primitive arrays.
1191
+
1192
+ **Key Techniques:**
1193
+ - `IntArray(size)`, `intArrayOf(1, 2, 3)` — JVM `int[]`, no boxing
1194
+ - `LongArray`, `DoubleArray`, `FloatArray`, `BooleanArray`, `ByteArray`, etc.
1195
+ - Significant performance difference in tight loops and large arrays
1196
+ - `IntArray` uses ~4 bytes per element; `Array<Int>` uses ~16 bytes per element (object + reference)
1197
+ - Can convert: `list.toIntArray()`, `intArray.toList()`
1198
+ - Standard operations work: `intArray.map { }`, `intArray.filter { }`, but these return `List<Int>` — for staying in primitive land, use indices and manual loops or specialized libraries
1199
+
1200
+ **When to Apply:** For numeric processing with large arrays: scientific computing,
1201
+ image processing, financial calculations, game engines, data analysis.
1202
+
1203
+ **Anti-Pattern:** Using `List<Int>` or `Array<Int>` for large numeric datasets where
1204
+ `IntArray` would avoid boxing overhead.
1205
+
1206
+ ---
1207
+
1208
+ #### Item 52: Consider Using Mutable Collections
1209
+
1210
+ **Category:** Efficiency — Collection Mutability for Performance
1211
+
1212
+ **Core Practice:** Immutable collections are preferred for safety, but in performance-critical
1213
+ local scopes, mutable collections with in-place operations can be significantly faster
1214
+ than immutable chains that create new collections at each step.
1215
+
1216
+ **Key Techniques:**
1217
+ - Local mutability: `buildList { }`, `buildMap { }`, `buildSet { }` — mutable inside, immutable result
1218
+ - `mutableListOf<T>()` + `add()` instead of `list + element` in a loop (avoiding O(n) copies)
1219
+ - In-place sorting: `list.sort()` (mutates) vs `list.sorted()` (creates new list)
1220
+ - For accumulation: `mutableMapOf<K, MutableList<V>>()` + `getOrPut { mutableListOf() }.add()`
1221
+ - Keep mutability local: function builds with mutable, returns immutable
1222
+ - API boundaries should use immutable types; internal hot paths can use mutable
1223
+
1224
+ **When to Apply:** In performance-sensitive code where collection operations are a bottleneck.
1225
+ Always keep the mutability scope as narrow as possible.
1226
+
1227
+ **Anti-Pattern:** Using `fold` to accumulate into new lists (O(n²) for n elements); using
1228
+ `+` in a loop to build a list; returning mutable collections from public APIs.