Summary
Small, pre-change tidies reduce risk, shrink review cost, and speed delivery. Separate structure changes from behavior changes; prefer explicitness, locality, and decoupling. :contentReference[oaicite:0]{index=0}
Core patterns → tiny examples
1) Lazy initialization
React/TS
const data = useMemo(() => fetchExpensive(id), [id]);
Go
type S struct{ once sync.Once; cli *Client }
func (s *S) Client() *Client { s.once.Do(func(){ s.cli = New() }); return s.cli }
2) Keep declaration next to initialization
React/TS
const [user, setUser] = useState<User|null>(null);
useEffect(()=>{ fetchUser(id).then(setUser); },[id]);
Go
order := getOrder(id)
items := getItems(id)
cust := getCustomer(order.CustomerID)
3) Prefer explicit parameters (avoid opaque config bags)
React/TS
function DataTable({data, pageSize, onRowClick}: {data:any[];pageSize:number;onRowClick?:(r:any)=>void}){ /* ... */ }
Go
func StartServer(port int, host, cert, level string) error { /* ... */ }
(Use an Options struct only for truly optional tails.)
4) Delete redundant comments via clearer code
React/TS
useEffect(()=>{ fetchUsers().then(setUsers); },[]);
Go
if err != nil { return nil, fmt.Errorf("query active users: %w", err) }
5) Separate Structure (S) vs Behavior (B)
-
PR 1 (S): extract hook / function; move code to better place.
-
PR 2 (B): add feature or change logic.
This cuts review cost and rollback risk.
React/TS (S → B)
// S
function useUser(id:string){ /* fetch + state */ }
// B
function Profile({id}:{id:string}){ const {user}=useUser(id); return <p>Role:{user?.role??"User"}</p> }
Go (S → B)
// S
func validate(t Tx) error { /* ... */ }; func compute(t Tx)(fee,total float64){ /* ... */ }
// B
logAudit(t); if err := save(t,total,fee); err != nil { return err }
6) Reduce coupling, raise cohesion
React/TS
function UserProfile({u}:{u:User}){ /* only profile */ }
function UserPosts({id}:{id:string}){ /* only posts */ }
Go (interfaces + DI)
type Inventory interface{ Stock(id string)(int,error) }
type Payment interface{ Pay(PaymentInfo) error }
// OrderService depends on interfaces, not concrete DB/Gateway.
7) Interface Definition Language mindset
Define the contract once; have sender/receiver use it.
React/TS
interface UserAPI{ me():Promise<User>; byId(id:string):Promise<User>; }
class RestUserAPI implements UserAPI { /* fetch */ }
class MockUserAPI implements UserAPI { /* in-mem */ }
Go
type UserRepo interface{ GetByID(ctx context.Context,id string)(*User,error); /* … */ }
Swap implementations without touching callers.
Why tidy first?
-
Shrinks batch size → cheaper reviews → more, safer change.
-
Moves expressions “into the box” (closer to the data/owner) → fewer call-site edits.
-
Most cost accrues from changes, so make each smaller and clearer.
Quick checklist (copy/paste)
-
Can I move this code nearer to its data/usage?
-
Is there a redundant comment I can delete by renaming/refactoring?
-
Are params explicit? Any “config bag” to split?
-
PR split into S then B?
-
Did I replace concrete deps with interfaces where beneficial?
Sources
- Kent Beck, _Tidy First?