@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.
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/animation-at-work/SKILL.md +246 -0
- package/animation-at-work/assets/example_asset.txt +1 -0
- package/animation-at-work/references/api_reference.md +369 -0
- package/animation-at-work/references/review-checklist.md +79 -0
- package/animation-at-work/scripts/example.py +1 -0
- package/bin/skills.js +85 -0
- package/clean-code-reviewer/SKILL.md +292 -0
- package/clean-code-reviewer/evals/evals.json +67 -0
- package/data-intensive-patterns/SKILL.md +204 -0
- package/data-intensive-patterns/assets/example_asset.txt +1 -0
- package/data-intensive-patterns/references/api_reference.md +34 -0
- package/data-intensive-patterns/references/patterns-catalog.md +551 -0
- package/data-intensive-patterns/references/review-checklist.md +193 -0
- package/data-intensive-patterns/scripts/example.py +1 -0
- package/data-pipelines/SKILL.md +252 -0
- package/data-pipelines/assets/example_asset.txt +1 -0
- package/data-pipelines/references/api_reference.md +301 -0
- package/data-pipelines/references/review-checklist.md +181 -0
- package/data-pipelines/scripts/example.py +1 -0
- package/design-patterns/SKILL.md +245 -0
- package/design-patterns/assets/example_asset.txt +1 -0
- package/design-patterns/references/api_reference.md +1 -0
- package/design-patterns/references/patterns-catalog.md +726 -0
- package/design-patterns/references/review-checklist.md +173 -0
- package/design-patterns/scripts/example.py +1 -0
- package/domain-driven-design/SKILL.md +221 -0
- package/domain-driven-design/assets/example_asset.txt +1 -0
- package/domain-driven-design/references/api_reference.md +1 -0
- package/domain-driven-design/references/patterns-catalog.md +545 -0
- package/domain-driven-design/references/review-checklist.md +158 -0
- package/domain-driven-design/scripts/example.py +1 -0
- package/effective-java/SKILL.md +195 -0
- package/effective-java/assets/example_asset.txt +1 -0
- package/effective-java/references/api_reference.md +1 -0
- package/effective-java/references/items-catalog.md +955 -0
- package/effective-java/references/review-checklist.md +216 -0
- package/effective-java/scripts/example.py +1 -0
- package/effective-kotlin/SKILL.md +225 -0
- package/effective-kotlin/assets/example_asset.txt +1 -0
- package/effective-kotlin/references/api_reference.md +1 -0
- package/effective-kotlin/references/practices-catalog.md +1228 -0
- package/effective-kotlin/references/review-checklist.md +126 -0
- package/effective-kotlin/scripts/example.py +1 -0
- package/kotlin-in-action/SKILL.md +251 -0
- package/kotlin-in-action/assets/example_asset.txt +1 -0
- package/kotlin-in-action/references/api_reference.md +1 -0
- package/kotlin-in-action/references/practices-catalog.md +436 -0
- package/kotlin-in-action/references/review-checklist.md +204 -0
- package/kotlin-in-action/scripts/example.py +1 -0
- package/lean-startup/SKILL.md +250 -0
- package/lean-startup/assets/example_asset.txt +1 -0
- package/lean-startup/references/api_reference.md +319 -0
- package/lean-startup/references/review-checklist.md +137 -0
- package/lean-startup/scripts/example.py +1 -0
- package/microservices-patterns/SKILL.md +179 -0
- package/microservices-patterns/references/patterns-catalog.md +391 -0
- package/microservices-patterns/references/review-checklist.md +169 -0
- package/package.json +17 -0
- package/refactoring-ui/SKILL.md +236 -0
- package/refactoring-ui/assets/example_asset.txt +1 -0
- package/refactoring-ui/references/api_reference.md +355 -0
- package/refactoring-ui/references/review-checklist.md +114 -0
- package/refactoring-ui/scripts/example.py +1 -0
- package/storytelling-with-data/SKILL.md +238 -0
- package/storytelling-with-data/assets/example_asset.txt +1 -0
- package/storytelling-with-data/references/api_reference.md +379 -0
- package/storytelling-with-data/references/review-checklist.md +111 -0
- package/storytelling-with-data/scripts/example.py +1 -0
- package/system-design-interview/SKILL.md +213 -0
- package/system-design-interview/assets/example_asset.txt +1 -0
- package/system-design-interview/references/api_reference.md +582 -0
- package/system-design-interview/references/review-checklist.md +201 -0
- package/system-design-interview/scripts/example.py +1 -0
- package/using-asyncio-python/SKILL.md +242 -0
- package/using-asyncio-python/assets/example_asset.txt +1 -0
- package/using-asyncio-python/references/api_reference.md +267 -0
- package/using-asyncio-python/references/review-checklist.md +149 -0
- package/using-asyncio-python/scripts/example.py +1 -0
- package/web-scraping-python/SKILL.md +259 -0
- package/web-scraping-python/assets/example_asset.txt +1 -0
- package/web-scraping-python/references/api_reference.md +393 -0
- package/web-scraping-python/references/review-checklist.md +163 -0
- 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.
|