Files of Naming conventions
wondelai/
Show the full text281 lines
Naming Conventions
Comprehensive guide to choosing names that reveal intent, avoid disinformation, and make code read like well-written prose. Based on Robert C. Martin's Clean Code, Chapter 2.
The Golden Rule
A name should tell you why it exists, what it does, and how it is used. If a name requires a comment to explain it, the name does not reveal its intent.
Intention-Revealing Names
The name of a variable, function, or class should answer three questions:
- Why does it exist?
- What does it do?
- How is it used?
Before and After
| Before | After | Why it's better |
|---|---|---|
d |
elapsedTimeInDays |
Reveals what the number represents |
list |
flaggedAccounts |
Describes which list and why it exists |
hp |
isHighPriority |
Self-documents its boolean nature and meaning |
temp |
unprocessedBatchItems |
Communicates purpose, not temporary status |
x, y |
latitude, longitude |
Domain language replaces abstract math symbols |
val |
discountPercentage |
Specifies what value and its unit |
data |
customerOrderHistory |
Names the specific data, not the generic concept |
info |
shippingTrackingDetails |
Adds specificity to a meaningless suffix |
Code Example
# BAD: What does this do?
def get_them(the_list):
result = []
for x in the_list:
if x[0] == 4:
result.append(x)
return result
# GOOD: Intention is revealed
def get_flagged_cells(game_board):
flagged_cells = []
for cell in game_board:
if cell.is_flagged():
flagged_cells.append(cell)
return flagged_cells
Avoiding Disinformation
Disinformation means using names that imply something different from the actual meaning.
Rules:
- Don't use
accountListunless it is actually aListtype -- useaccountsoraccountGroup - Don't use names that vary in small ways:
XYZControllerForEfficientHandlingOfStringsvsXYZControllerForEfficientStorageOfStrings - Don't use lowercase
Lor uppercaseOas variable names (look like1and0) - Don't use names of other well-known entities:
hp,aix,scoare Unix platform names
Common Disinformation Patterns
| Disinformation | Problem | Fix |
|---|---|---|
accountList when it's a Set |
Implies ordered, duplicates allowed | Use accounts |
name1, name2 |
Implies ordering or relationship that doesn't exist | Use meaningful distinction: firstName, lastName |
strName |
Hungarian notation; type may change | Just use name |
theAccount vs account |
No meaningful distinction | Pick one, use it consistently |
Meaningful Distinctions
If names must be different, they should mean something different.
Noise words are meaningless distinctions:
ProductvsProductInfovsProductData-- what's the difference?getAccount()vsgetAccountInfo()vsgetAccountData()-- indistinguishablenamevsnameString-- isnamea floating-point number?
The test: If you cannot tell what two similarly named things do without reading their implementations, the names fail.
How to Make Meaningful Distinctions
| Instead of | Use | Rationale |
|---|---|---|
copyChars(a1, a2) |
copyChars(source, destination) |
Arguments reveal their role |
moneyAmount vs money |
Just money |
Amount is implied by the context |
theMessage vs message |
Just message |
The article adds no information |
accountData vs account |
Just account |
Data is inherent -- what else would it be? |
Pronounceable Names
If you cannot pronounce a name, you cannot discuss it without sounding foolish.
| Unpronounceable | Pronounceable |
|---|---|
genymdhms |
generationTimestamp |
modymdhms |
modificationTimestamp |
pszqint |
formattedQuantity |
cstmrAddr |
customerAddress |
Why it matters: Programming is a social activity. You discuss code in reviews, pairing, design meetings, and stand-ups. Names you cannot say out loud create communication barriers.
Searchable Names
Single-letter names and numeric constants are impossible to search for in a large codebase.
Rule of thumb: The length of a name should correspond to the size of its scope.
| Scope | Name length | Example |
|---|---|---|
| Single-line lambda | 1 character acceptable | items.map(x => x.id) |
| Loop body (3-5 lines) | 1-2 characters acceptable | for i in range(len(items)) |
| Method scope | Descriptive word(s) | retryCount, matchingUser |
| Class scope | Fully descriptive phrase | maximumQueueCapacity |
| Module/global scope | Complete, searchable phrase | DEFAULT_CONNECTION_TIMEOUT_MS |
Constants Must Be Named
// BAD: What does 5 mean? What does 7 mean?
if (retries > 5) { ... }
setTimeout(fn, 7 * 86400000);
// GOOD: Self-documenting
const MAX_RETRY_ATTEMPTS = 5;
const CACHE_EXPIRY_DAYS = 7;
const MS_PER_DAY = 86400000;
if (retries > MAX_RETRY_ATTEMPTS) { ... }
setTimeout(fn, CACHE_EXPIRY_DAYS * MS_PER_DAY);
Class Names vs. Method Names
Class Names: Nouns and Noun Phrases
Classes represent things. Their names should be nouns or noun phrases.
| Good | Bad | Why |
|---|---|---|
Customer |
CustomerManager |
Manager is vague -- what does it manage? |
WikiPage |
WebPage |
More specific to the domain |
AddressParser |
ParseAddress |
Noun phrase, not a verb |
InvoiceRepository |
InvoiceProcessor |
Processor is vague -- what processing? |
Account |
AccountData |
Data suffix is noise |
Avoid: Manager, Processor, Data, Info -- these are weasel words that indicate the class has no clear responsibility.
Method Names: Verbs and Verb Phrases
Methods represent actions. Their names should be verbs or verb phrases.
| Good | Bad | Why |
|---|---|---|
save() |
doSave() |
do prefix is noise |
calculateTax() |
tax() |
Verb clarifies it computes, not stores |
isValid() |
checkValid() |
Boolean accessor follows is/has/can convention |
fromJson(str) |
parse(str) |
Static factory name describes transformation |
deleteExpiredSessions() |
cleanup() |
Specific about what gets cleaned |
Accessor, Mutator, and Predicate Conventions
// Accessors: get + property
String getName()
int getAge()
// Mutators: set + property
void setName(String name)
void setAge(int age)
// Predicates: is/has/can + condition
boolean isEmpty()
boolean hasPermission(Role role)
boolean canExecute()
Naming Conventions by Language
Python
snake_casefor functions and variables:calculate_total,user_countPascalCasefor classes:UserAccount,OrderProcessorUPPER_SNAKE_CASEfor constants:MAX_RETRIES,DEFAULT_TIMEOUT_leading_underscorefor private:_internal_cache__dunder__for magic methods:__init__,__str__
JavaScript/TypeScript
camelCasefor functions and variables:calculateTotal,userCountPascalCasefor classes and components:UserAccount,OrderListUPPER_SNAKE_CASEfor constants:MAX_RETRIES,API_BASE_URL#privateFieldfor private class fields (ES2022+)
Java
camelCasefor methods and variables:calculateTotal(),userCountPascalCasefor classes:UserAccount,OrderServiceUPPER_SNAKE_CASEfor constants:MAX_RETRIES- Packages:
com.company.project.module
Go
PascalCasefor exported (public):CalculateTotal,UserCountcamelCasefor unexported (private):calculateTotal,userCount- Short names acceptable for small scopes:
rfor reader,wfor writer - Acronyms stay uppercase:
HTTPClient,XMLParser,userID
Rust
snake_casefor functions and variables:calculate_total,user_countPascalCasefor types and traits:UserAccount,SerializeUPPER_SNAKE_CASEfor constants and statics:MAX_RETRIES- Lifetime names: short lowercase
'a,'bor descriptive'input,'output
The Pick-One-Word-Per-Concept Rule
Using different words for the same abstract concept is confusing.
| Inconsistent | Consistent | Rule |
|---|---|---|
fetch / retrieve / get |
Pick get everywhere |
One word for the read operation |
controller / manager / driver |
Pick controller everywhere |
One word for the coordination role |
create / make / build / new |
Pick create for API, build for complex assembly |
Differentiate only if semantics differ |
remove / delete / destroy |
delete for permanent, remove for detach |
Different words when semantics differ |
Solution Domain vs. Problem Domain Names
Use solution domain names when the concept is a well-known computer science term:
Queue,Stack,HashMap,Factory,Visitor,Iterator- Fellow programmers will recognize these instantly
Use problem domain names when the concept belongs to the business:
Invoice,Shipment,PolicyHolder,ClaimAdjuster- Domain experts and future maintainers will understand these
The priority: Problem domain names first, solution domain names second. Code that reads in business terms is easier for the whole team -- developers, testers, product managers -- to discuss and verify.
Common Anti-Patterns
Names to Avoid
| Anti-pattern | Example | Problem |
|---|---|---|
| Single letter | a, b, t |
Meaningless outside tiny loops |
| Abbreviation | acct, mgr, btn, usr |
Saves milliseconds, costs hours |
| Generic suffix | DataObject, InfoManager |
Adds nothing; signals unclear responsibility |
| Type encoding | sName, iCount, bFlag |
IDE shows types; encoding is obsolete and misleading |
| Numbered series | arg1, arg2, arg3 |
Provides no information about each argument's role |
| Mental mapping | r means URL (in the author's head) |
Forces readers to maintain a mental translation table |
| Puns | add meaning both "concatenate" and "insert" |
Same word, different semantics creates confusion |
Renaming Checklist
When renaming a variable, function, or class, follow this checklist to ensure the change improves clarity:
- Does the new name reveal intent? The reader should understand purpose without reading the implementation.
- Is it pronounceable? Can you say it in a code review without spelling it out?
- Is it searchable? Can you find all occurrences with a simple text search?
- Does it avoid disinformation? The name should not imply something the code does not do.
- Is it consistent with the codebase? Use the same word for the same concept everywhere.
- Is the length proportional to scope? Short names for tiny scopes, long names for large scopes.
- Does it use domain language? Prefer business terminology over generic programming terms.
Remember: Renaming is one of the most powerful refactorings. Modern IDEs make it safe and fast. Never hesitate to rename something that is unclear -- your future self and your teammates will thank you.
| 1 | # Naming Conventions |
| 2 | |
| 3 | Comprehensive guide to choosing names that reveal intent, avoid disinformation, and make code read like well-written prose. Based on Robert C. Martin's *Clean Code*, Chapter 2. |
| 4 | |
| 5 | ## The Golden Rule |
| 6 | |
| 7 | **A name should tell you why it exists, what it does, and how it is used.** If a name requires a comment to explain it, the name does not reveal its intent. |
| 8 | |
| 9 | |
| 10 | |
| 11 | ## Intention-Revealing Names |
| 12 | |
| 13 | The name of a variable, function, or class should answer three questions: |
| 14 | Why does it exist? |
| 15 | What does it do? |
| 16 | How is it used? |
| 17 | |
| 18 | ### Before and After |
| 19 | |
| 20 | | Before | After | Why it's better | |
| 21 | |--------|-------|-----------------| |
| 22 | | `d` | `elapsedTimeInDays` | Reveals what the number represents | |
| 23 | | `list` | `flaggedAccounts` | Describes *which* list and *why* it exists | |
| 24 | | `hp` | `isHighPriority` | Self-documents its boolean nature and meaning | |
| 25 | | `temp` | `unprocessedBatchItems` | Communicates purpose, not temporary status | |
| 26 | | `x`, `y` | `latitude`, `longitude` | Domain language replaces abstract math symbols | |
| 27 | | `val` | `discountPercentage` | Specifies what value and its unit | |
| 28 | | `data` | `customerOrderHistory` | Names the specific data, not the generic concept | |
| 29 | | `info` | `shippingTrackingDetails` | Adds specificity to a meaningless suffix | |
| 30 | |
| 31 | ### Code Example |
| 32 | |
| 33 | |
| 34 | # BAD: What does this do? |
| 35 | def get_them(the_list): |
| 36 | result = [] |
| 37 | for x in the_list: |
| 38 | if x[0] == 4: |
| 39 | result.append(x) |
| 40 | return result |
| 41 | |
| 42 | # GOOD: Intention is revealed |
| 43 | def get_flagged_cells(game_board): |
| 44 | flagged_cells = [] |
| 45 | for cell in game_board: |
| 46 | if cell.is_flagged(): |
| 47 | flagged_cells.append(cell) |
| 48 | return flagged_cells |
| 49 | |
| 50 | |
| 51 | |
| 52 | |
| 53 | ## Avoiding Disinformation |
| 54 | |
| 55 | Disinformation means using names that imply something different from the actual meaning. |
| 56 | |
| 57 | **Rules:** |
| 58 | Don't use `accountList` unless it is actually a `List` type -- use `accounts` or `accountGroup` |
| 59 | Don't use names that vary in small ways: `XYZControllerForEfficientHandlingOfStrings` vs `XYZControllerForEfficientStorageOfStrings` |
| 60 | Don't use lowercase `L` or uppercase `O` as variable names (look like `1` and `0`) |
| 61 | Don't use names of other well-known entities: `hp`, `aix`, `sco` are Unix platform names |
| 62 | |
| 63 | ### Common Disinformation Patterns |
| 64 | |
| 65 | | Disinformation | Problem | Fix | |
| 66 | |----------------|---------|-----| |
| 67 | | `accountList` when it's a `Set` | Implies ordered, duplicates allowed | Use `accounts` | |
| 68 | | `name1`, `name2` | Implies ordering or relationship that doesn't exist | Use meaningful distinction: `firstName`, `lastName` | |
| 69 | | `strName` | Hungarian notation; type may change | Just use `name` | |
| 70 | | `theAccount` vs `account` | No meaningful distinction | Pick one, use it consistently | |
| 71 | |
| 72 | |
| 73 | |
| 74 | ## Meaningful Distinctions |
| 75 | |
| 76 | If names must be different, they should mean something different. |
| 77 | |
| 78 | **Noise words are meaningless distinctions:** |
| 79 | `Product` vs `ProductInfo` vs `ProductData` -- what's the difference? |
| 80 | `getAccount()` vs `getAccountInfo()` vs `getAccountData()` -- indistinguishable |
| 81 | `name` vs `nameString` -- is `name` a floating-point number? |
| 82 | |
| 83 | **The test:** If you cannot tell what two similarly named things do without reading their implementations, the names fail. |
| 84 | |
| 85 | ### How to Make Meaningful Distinctions |
| 86 | |
| 87 | | Instead of | Use | Rationale | |
| 88 | |------------|-----|-----------| |
| 89 | | `copyChars(a1, a2)` | `copyChars(source, destination)` | Arguments reveal their role | |
| 90 | | `moneyAmount` vs `money` | Just `money` | Amount is implied by the context | |
| 91 | | `theMessage` vs `message` | Just `message` | The article adds no information | |
| 92 | | `accountData` vs `account` | Just `account` | Data is inherent -- what else would it be? | |
| 93 | |
| 94 | |
| 95 | |
| 96 | ## Pronounceable Names |
| 97 | |
| 98 | If you cannot pronounce a name, you cannot discuss it without sounding foolish. |
| 99 | |
| 100 | | Unpronounceable | Pronounceable | |
| 101 | |-----------------|---------------| |
| 102 | | `genymdhms` | `generationTimestamp` | |
| 103 | | `modymdhms` | `modificationTimestamp` | |
| 104 | | `pszqint` | `formattedQuantity` | |
| 105 | | `cstmrAddr` | `customerAddress` | |
| 106 | |
| 107 | **Why it matters:** Programming is a social activity. You discuss code in reviews, pairing, design meetings, and stand-ups. Names you cannot say out loud create communication barriers. |
| 108 | |
| 109 | |
| 110 | |
| 111 | ## Searchable Names |
| 112 | |
| 113 | Single-letter names and numeric constants are impossible to search for in a large codebase. |
| 114 | |
| 115 | **Rule of thumb:** The length of a name should correspond to the size of its scope. |
| 116 | |
| 117 | | Scope | Name length | Example | |
| 118 | |-------|-------------|---------| |
| 119 | | Single-line lambda | 1 character acceptable | `items.map(x => x.id)` | |
| 120 | | Loop body (3-5 lines) | 1-2 characters acceptable | `for i in range(len(items))` | |
| 121 | | Method scope | Descriptive word(s) | `retryCount`, `matchingUser` | |
| 122 | | Class scope | Fully descriptive phrase | `maximumQueueCapacity` | |
| 123 | | Module/global scope | Complete, searchable phrase | `DEFAULT_CONNECTION_TIMEOUT_MS` | |
| 124 | |
| 125 | ### Constants Must Be Named |
| 126 | |
| 127 | |
| 128 | // BAD: What does 5 mean? What does 7 mean? |
| 129 | if (retries > 5) { ... } |
| 130 | setTimeout(fn, 7 * 86400000); |
| 131 | |
| 132 | // GOOD: Self-documenting |
| 133 | const MAX_RETRY_ATTEMPTS = 5; |
| 134 | const CACHE_EXPIRY_DAYS = 7; |
| 135 | const MS_PER_DAY = 86400000; |
| 136 | |
| 137 | if (retries > MAX_RETRY_ATTEMPTS) { ... } |
| 138 | setTimeout(fn, CACHE_EXPIRY_DAYS * MS_PER_DAY); |
| 139 | |
| 140 | |
| 141 | |
| 142 | |
| 143 | ## Class Names vs. Method Names |
| 144 | |
| 145 | ### Class Names: Nouns and Noun Phrases |
| 146 | |
| 147 | Classes represent things. Their names should be nouns or noun phrases. |
| 148 | |
| 149 | | Good | Bad | Why | |
| 150 | |------|-----|-----| |
| 151 | | `Customer` | `CustomerManager` | Manager is vague -- what does it manage? | |
| 152 | | `WikiPage` | `WebPage` | More specific to the domain | |
| 153 | | `AddressParser` | `ParseAddress` | Noun phrase, not a verb | |
| 154 | | `InvoiceRepository` | `InvoiceProcessor` | Processor is vague -- what processing? | |
| 155 | | `Account` | `AccountData` | Data suffix is noise | |
| 156 | |
| 157 | **Avoid:** `Manager`, `Processor`, `Data`, `Info` -- these are weasel words that indicate the class has no clear responsibility. |
| 158 | |
| 159 | ### Method Names: Verbs and Verb Phrases |
| 160 | |
| 161 | Methods represent actions. Their names should be verbs or verb phrases. |
| 162 | |
| 163 | | Good | Bad | Why | |
| 164 | |------|-----|-----| |
| 165 | | `save()` | `doSave()` | `do` prefix is noise | |
| 166 | | `calculateTax()` | `tax()` | Verb clarifies it computes, not stores | |
| 167 | | `isValid()` | `checkValid()` | Boolean accessor follows `is`/`has`/`can` convention | |
| 168 | | `fromJson(str)` | `parse(str)` | Static factory name describes transformation | |
| 169 | | `deleteExpiredSessions()` | `cleanup()` | Specific about what gets cleaned | |
| 170 | |
| 171 | ### Accessor, Mutator, and Predicate Conventions |
| 172 | |
| 173 | |
| 174 | // Accessors: get + property |
| 175 | String getName() |
| 176 | int getAge() |
| 177 | |
| 178 | // Mutators: set + property |
| 179 | void setName(String name) |
| 180 | void setAge(int age) |
| 181 | |
| 182 | // Predicates: is/has/can + condition |
| 183 | boolean isEmpty() |
| 184 | boolean hasPermission(Role role) |
| 185 | boolean canExecute() |
| 186 | |
| 187 | |
| 188 | |
| 189 | |
| 190 | ## Naming Conventions by Language |
| 191 | |
| 192 | ### Python |
| 193 | `snake_case` for functions and variables: `calculate_total`, `user_count` |
| 194 | `PascalCase` for classes: `UserAccount`, `OrderProcessor` |
| 195 | `UPPER_SNAKE_CASE` for constants: `MAX_RETRIES`, `DEFAULT_TIMEOUT` |
| 196 | `_leading_underscore` for private: `_internal_cache` |
| 197 | `__dunder__` for magic methods: `__init__`, `__str__` |
| 198 | |
| 199 | ### JavaScript/TypeScript |
| 200 | `camelCase` for functions and variables: `calculateTotal`, `userCount` |
| 201 | `PascalCase` for classes and components: `UserAccount`, `OrderList` |
| 202 | `UPPER_SNAKE_CASE` for constants: `MAX_RETRIES`, `API_BASE_URL` |
| 203 | `#privateField` for private class fields (ES2022+) |
| 204 | |
| 205 | ### Java |
| 206 | `camelCase` for methods and variables: `calculateTotal()`, `userCount` |
| 207 | `PascalCase` for classes: `UserAccount`, `OrderService` |
| 208 | `UPPER_SNAKE_CASE` for constants: `MAX_RETRIES` |
| 209 | Packages: `com.company.project.module` |
| 210 | |
| 211 | ### Go |
| 212 | `PascalCase` for exported (public): `CalculateTotal`, `UserCount` |
| 213 | `camelCase` for unexported (private): `calculateTotal`, `userCount` |
| 214 | Short names acceptable for small scopes: `r` for reader, `w` for writer |
| 215 | Acronyms stay uppercase: `HTTPClient`, `XMLParser`, `userID` |
| 216 | |
| 217 | ### Rust |
| 218 | `snake_case` for functions and variables: `calculate_total`, `user_count` |
| 219 | `PascalCase` for types and traits: `UserAccount`, `Serialize` |
| 220 | `UPPER_SNAKE_CASE` for constants and statics: `MAX_RETRIES` |
| 221 | Lifetime names: short lowercase `'a`, `'b` or descriptive `'input`, `'output` |
| 222 | |
| 223 | |
| 224 | |
| 225 | ## The Pick-One-Word-Per-Concept Rule |
| 226 | |
| 227 | Using different words for the same abstract concept is confusing. |
| 228 | |
| 229 | | Inconsistent | Consistent | Rule | |
| 230 | |-------------|------------|------| |
| 231 | | `fetch` / `retrieve` / `get` | Pick `get` everywhere | One word for the read operation | |
| 232 | | `controller` / `manager` / `driver` | Pick `controller` everywhere | One word for the coordination role | |
| 233 | | `create` / `make` / `build` / `new` | Pick `create` for API, `build` for complex assembly | Differentiate only if semantics differ | |
| 234 | | `remove` / `delete` / `destroy` | `delete` for permanent, `remove` for detach | Different words when semantics differ | |
| 235 | |
| 236 | |
| 237 | |
| 238 | ## Solution Domain vs. Problem Domain Names |
| 239 | |
| 240 | **Use solution domain names** when the concept is a well-known computer science term: |
| 241 | `Queue`, `Stack`, `HashMap`, `Factory`, `Visitor`, `Iterator` |
| 242 | Fellow programmers will recognize these instantly |
| 243 | |
| 244 | **Use problem domain names** when the concept belongs to the business: |
| 245 | `Invoice`, `Shipment`, `PolicyHolder`, `ClaimAdjuster` |
| 246 | Domain experts and future maintainers will understand these |
| 247 | |
| 248 | **The priority:** Problem domain names first, solution domain names second. Code that reads in business terms is easier for the whole team -- developers, testers, product managers -- to discuss and verify. |
| 249 | |
| 250 | |
| 251 | |
| 252 | ## Common Anti-Patterns |
| 253 | |
| 254 | ### Names to Avoid |
| 255 | |
| 256 | | Anti-pattern | Example | Problem | |
| 257 | |-------------|---------|---------| |
| 258 | | **Single letter** | `a`, `b`, `t` | Meaningless outside tiny loops | |
| 259 | | **Abbreviation** | `acct`, `mgr`, `btn`, `usr` | Saves milliseconds, costs hours | |
| 260 | | **Generic suffix** | `DataObject`, `InfoManager` | Adds nothing; signals unclear responsibility | |
| 261 | | **Type encoding** | `sName`, `iCount`, `bFlag` | IDE shows types; encoding is obsolete and misleading | |
| 262 | | **Numbered series** | `arg1`, `arg2`, `arg3` | Provides no information about each argument's role | |
| 263 | | **Mental mapping** | `r` means URL (in the author's head) | Forces readers to maintain a mental translation table | |
| 264 | | **Puns** | `add` meaning both "concatenate" and "insert" | Same word, different semantics creates confusion | |
| 265 | |
| 266 | |
| 267 | |
| 268 | ## Renaming Checklist |
| 269 | |
| 270 | When renaming a variable, function, or class, follow this checklist to ensure the change improves clarity: |
| 271 | |
| 272 | **Does the new name reveal intent?** The reader should understand purpose without reading the implementation. |
| 273 | **Is it pronounceable?** Can you say it in a code review without spelling it out? |
| 274 | **Is it searchable?** Can you find all occurrences with a simple text search? |
| 275 | **Does it avoid disinformation?** The name should not imply something the code does not do. |
| 276 | **Is it consistent with the codebase?** Use the same word for the same concept everywhere. |
| 277 | **Is the length proportional to scope?** Short names for tiny scopes, long names for large scopes. |
| 278 | **Does it use domain language?** Prefer business terminology over generic programming terms. |
| 279 | |
| 280 | **Remember:** Renaming is one of the most powerful refactorings. Modern IDEs make it safe and fast. Never hesitate to rename something that is unclear -- your future self and your teammates will thank you. |
| 281 |
Discussion
Browse more free Claude skills.