Clean Code & Performance

ປຽບທຽບໂຄດແບບທຳມະດາ ກັບໂຄດທີ່ຖືກປັບປຸງໃຫ້ສະອາດ ແລະ ໄວຂຶ້ນ — ຝັ່ງຊ້າຍຄືກ່ອນປັບ, ຝັ່ງຂວາຄືຫຼັງປັບ

Frontend — React / Next.js


01

ຫຼີກເວັ້ນ State ຊ້ຳຊ້ອນ — ໃຊ້ useMemo ແທນ useEffect

ຄ່າທີ່ຄິດໄລ່ໄດ້ຈາກ props/state ຢູ່ແລ້ວ ບໍ່ຄວນເກັບໃສ່ state ອີກອັນຜ່ານ useEffect ເພາະຈະ render ຊ້ຳສອງເທື່ອທຸກຄັ້ງທີ່ພິມ. ຄິດໄລ່ມັນລະຫວ່າງ render ດ້ວຍ useMemo ດີກວ່າ.

PerformanceReact Hooks
ກ່ອນປັບ — ແບບທຳມະດາ
1function ProductList({ products }) {
2 const [query, setQuery] = useState('')
3 const [filtered, setFiltered] = useState([])
4
5 // redundant state + useEffect
6 // -> renders twice on every keystroke
7 useEffect(() => {
8 setFiltered(
9 products.filter((p) =>
10 p.name.toLowerCase().includes(query.toLowerCase())
11 )
12 )
13 }, [products, query])
14
15 return (
16 <div>
17 <input value={query} onChange={(e) => setQuery(e.target.value)} />
18 {filtered.map((p) => (
19 <ProductItem key={p.id} product={p} />
20 ))}
21 </div>
22 )
23}
ຫຼັງປັບ — Clean & Fast
1function ProductList({ products }) {
2 const [query, setQuery] = useState('')
3
4 // derived state: computed during render,
5 // no extra state, no extra render
6 const filtered = useMemo(() => {
7 const q = query.toLowerCase()
8 return products.filter((p) => p.name.toLowerCase().includes(q))
9 }, [products, query])
10
11 return (
12 <div>
13 <input value={query} onChange={(e) => setQuery(e.target.value)} />
14 {filtered.map((p) => (
15 <ProductItem key={p.id} product={p} />
16 ))}
17 </div>
18 )
19}
02

Early Return ແທນ Ternary ຊ້ອນກັນ

Ternary ຊ້ອນກັນຫຼາຍຊັ້ນອ່ານຍາກ ແລະ ແກ້ບັກຍາກ. ໃຊ້ guard clause return ອອກກ່ອນ ເຮັດໃຫ້ເສັ້ນທາງຫຼັກຂອງ component ຈະແຈ້ງຂຶ້ນຫຼາຍ.

Readability
ກ່ອນປັບ — ແບບທຳມະດາ
1function UserCard({ user, isLoading, error }) {
2 return (
3 <div>
4 {isLoading ? (
5 <Spinner />
6 ) : error ? (
7 <p>{error.message}</p>
8 ) : user ? (
9 <div>
10 <h2>{user.name}</h2>
11 {user.isAdmin ? (
12 <AdminBadge />
13 ) : user.isPro ? (
14 <ProBadge />
15 ) : null}
16 </div>
17 ) : (
18 <p>No user found</p>
19 )}
20 </div>
21 )
22}
ຫຼັງປັບ — Clean & Fast
1function UserCard({ user, isLoading, error }) {
2 // guard clauses: fail fast, no nesting
3 if (isLoading) return <Spinner />
4 if (error) return <p>{error.message}</p>
5 if (!user) return <p>No user found</p>
6
7 return (
8 <div>
9 <h2>{user.name}</h2>
10 <UserBadge role={user.role} />
11 </div>
12 )
13}
03

ຢຸດ Re-render ທີ່ບໍ່ຈຳເປັນໃນ List

ໃຊ້ index ເປັນ key ແລະ ສ້າງ function/object ໃໝ່ທຸກ render ເຮັດໃຫ້ທຸກ item render ຄືນໝົດເວລາກົດອັນດຽວ. ໃຊ້ key ຄົງທີ່ + memo + useCallback ໃຫ້ render ສະເພາະ item ທີ່ປ່ຽນແທ້ໆ.

PerformanceReact.memo
ກ່ອນປັບ — ແບບທຳມະດາ
1function TodoList({ todos }) {
2 const [selectedId, setSelectedId] = useState(null)
3
4 return (
5 <ul>
6 {todos.map((todo, index) => (
7 // index as key + new function/object
8 // created on every render
9 // -> every item re-renders on each click
10 <TodoItem
11 key={index}
12 todo={todo}
13 style={{ padding: 8, borderRadius: 6 }}
14 onSelect={() => setSelectedId(todo.id)}
15 />
16 ))}
17 </ul>
18 )
19}
ຫຼັງປັບ — Clean & Fast
1const TodoItem = memo(function TodoItem({ todo, onSelect }) {
2 return <li onClick={() => onSelect(todo.id)}>{todo.title}</li>
3})
4
5const itemStyle = { padding: 8, borderRadius: 6 }
6
7function TodoList({ todos }) {
8 const [selectedId, setSelectedId] = useState(null)
9
10 // stable reference -> memo() can skip
11 // items whose props did not change
12 const handleSelect = useCallback((id) => setSelectedId(id), [])
13
14 return (
15 <ul>
16 {todos.map((todo) => (
17 <TodoItem key={todo.id} todo={todo} onSelect={handleSelect} />
18 ))}
19 </ul>
20 )
21}
04

ລວມ Logic ການ Fetch ໄວ້ໃນ Custom Hook

ໂຄດ fetch ທີ່ copy ຊ້ຳກັນທຸກ component ແລະ ບໍ່ຍົກເລີກ request ເກົ່າ ອາດເຮັດໃຫ້ຂໍ້ມູນເກົ່າມາທັບຂໍ້ມູນໃໝ່ (race condition). Custom hook + AbortController ແກ້ທັງສອງບັນຫາໃນບ່ອນດຽວ.

DRYRace Condition
ກ່ອນປັບ — ແບບທຳມະດາ
1function BlogPage() {
2 const [posts, setPosts] = useState([])
3 const [loading, setLoading] = useState(true)
4
5 useEffect(() => {
6 // same logic copy-pasted in every page,
7 // no cleanup: a slow old request can
8 // overwrite a newer response
9 fetch('/api/posts')
10 .then((res) => res.json())
11 .then((data) => {
12 setPosts(data)
13 setLoading(false)
14 })
15 }, [])
16
17 if (loading) return <Spinner />
18 return <PostGrid posts={posts} />
19}
ຫຼັງປັບ — Clean & Fast
1function useFetch(url) {
2 const [state, setState] = useState({
3 data: null, loading: true, error: null,
4 })
5
6 useEffect(() => {
7 const controller = new AbortController()
8 fetch(url, { signal: controller.signal })
9 .then((res) => res.json())
10 .then((data) => setState({ data, loading: false, error: null }))
11 .catch((error) => {
12 if (error.name !== 'AbortError')
13 setState({ data: null, loading: false, error })
14 })
15 // cancel the stale request on unmount / url change
16 return () => controller.abort()
17 }, [url])
18
19 return state
20}
21
22function BlogPage() {
23 const { data: posts, loading } = useFetch('/api/posts')
24
25 if (loading) return <Spinner />
26 return <PostGrid posts={posts} />
27}
05

ຫຼຸດຂະໜາດ Bundle ດ້ວຍ Dynamic Import

Import library ໜັກໆແບບ static ເຮັດໃຫ້ໜ້າທຳອິດໂຫຼດຊ້າ ເຖິງແມ່ນຜູ້ໃຊ້ບໍ່ເຄີຍໃຊ້ feature ນັ້ນເລີຍ. next/dynamic ຈະແຍກ bundle ແລະ ໂຫຼດສະເພາະຕອນຕ້ອງໃຊ້ແທ້ໆ.

PerformanceCode Splitting
ກ່ອນປັບ — ແບບທຳມະດາ
1import HeavyChart from 'heavy-chart-lib' // 300kB+
2import ExportPDF from './ExportPDF' // rarely used
3
4function Dashboard({ stats }) {
5 const [showExport, setShowExport] = useState(false)
6
7 // everything ships in the first bundle,
8 // slow first paint even if the user
9 // never opens the export dialog
10 return (
11 <div>
12 <HeavyChart data={stats} />
13 <button onClick={() => setShowExport(true)}>Export</button>
14 {showExport && <ExportPDF data={stats} />}
15 </div>
16 )
17}
ຫຼັງປັບ — Clean & Fast
1import dynamic from 'next/dynamic'
2
3// loaded only when rendered -> smaller first bundle
4const HeavyChart = dynamic(() => import('heavy-chart-lib'), {
5 loading: () => <ChartSkeleton />,
6 ssr: false,
7})
8const ExportPDF = dynamic(() => import('./ExportPDF'))
9
10function Dashboard({ stats }) {
11 const [showExport, setShowExport] = useState(false)
12
13 return (
14 <div>
15 <HeavyChart data={stats} />
16 <button onClick={() => setShowExport(true)}>Export</button>
17 {showExport && <ExportPDF data={stats} />}
18 </div>
19 )
20}

Backend — Node.js / Express / MongoDB


01

ແກ້ບັນຫາ N+1 Query

Query ຖານຂໍ້ມູນໃນ loop ເຮັດໃຫ້ 100 ລາຍການ = 101 ຄັ້ງຕໍ່ຖານຂໍ້ມູນ. ດຶງມາເທື່ອດຽວດ້ວຍ $in ແລ້ວ join ໃນ memory — ເຫຼືອພຽງ 2 queries ບໍ່ວ່າຈະມີຈັກລາຍການ.

PerformanceDatabase
ກ່ອນປັບ — ແບບທຳມະດາ
1app.get('/api/orders', async (req, res) => {
2 const orders = await Order.find()
3
4 // N+1 problem: 1 query + 1 more per order
5 // 100 orders = 101 database round trips
6 for (const order of orders) {
7 order.customer = await User.findById(order.userId)
8 }
9
10 res.json(orders)
11})
ຫຼັງປັບ — Clean & Fast
1app.get('/api/orders', async (req, res) => {
2 const orders = await Order.find().lean()
3
4 // 2 queries total, no matter how many orders
5 const userIds = orders.map((o) => o.userId)
6 const users = await User.find({ _id: { $in: userIds } }).lean()
7 const userById = new Map(users.map((u) => [String(u._id), u]))
8
9 res.json(
10 orders.map((o) => ({
11 ...o,
12 customer: userById.get(String(o.userId)),
13 }))
14 )
15})
02

ໃຊ້ Promise.all ກັບວຽກທີ່ບໍ່ຂຶ້ນຕໍ່ກັນ

await ຕໍ່ແຖວກັນເຮັດໃຫ້ເວລາລວມ = ຜົນບວກຂອງທຸກ query. ຖ້າ query ບໍ່ຂຶ້ນຕໍ່ກັນ ໃຫ້ run ພ້ອມກັນ — ເວລາລວມຈະເທົ່າກັບ query ທີ່ຊ້າສຸດອັນດຽວ.

PerformanceAsync
ກ່ອນປັບ — ແບບທຳມະດາ
1app.get('/api/dashboard', async (req, res) => {
2 // each await waits for the previous one
3 // total time = query1 + query2 + query3
4 const user = await User.findById(req.userId)
5 const orders = await Order.find({ userId: req.userId })
6 const alerts = await Notification.find({ userId: req.userId })
7
8 res.json({ user, orders, alerts })
9})
ຫຼັງປັບ — Clean & Fast
1app.get('/api/dashboard', async (req, res) => {
2 // independent queries run in parallel
3 // total time = the slowest query only
4 const [user, orders, alerts] = await Promise.all([
5 User.findById(req.userId),
6 Order.find({ userId: req.userId }),
7 Notification.find({ userId: req.userId }),
8 ])
9
10 res.json({ user, orders, alerts })
11})
03

Pagination + ເລືອກສະເພາະ Field ທີ່ໃຊ້

ດຶງທຸກ record ທຸກ field ຈະຊ້າລົງເລື້ອຍໆເມື່ອຂໍ້ມູນເພີ່ມຂຶ້ນ. ໃຊ້ limit/skip + select ດຶງສະເພາະໜ້າດຽວ ແລະ ສະເພາະ field ທີ່ໜ້າ list ຕ້ອງໃຊ້ແທ້.

PerformanceAPI Design
ກ່ອນປັບ — ແບບທຳມະດາ
1app.get('/api/products', async (req, res) => {
2 // loads EVERY product with EVERY field
3 // into memory on each request
4 // -> slow response + heavy payload
5 // that keeps growing with the data
6 const products = await Product.find()
7
8 res.json(products)
9})
ຫຼັງປັບ — Clean & Fast
1app.get('/api/products', async (req, res) => {
2 const page = Math.max(1, Number(req.query.page) || 1)
3 const limit = Math.min(50, Number(req.query.limit) || 20)
4
5 // one page only, with just the fields the list needs
6 const [items, total] = await Promise.all([
7 Product.find()
8 .select('name price thumbnail')
9 .skip((page - 1) * limit)
10 .limit(limit)
11 .lean(),
12 Product.countDocuments(),
13 ])
14
15 res.json({ items, total, page, pages: Math.ceil(total / limit) })
16})
04

Guard Clause + ຈັດການ Error ໃຫ້ປອດໄພ

if ຊ້ອນກັນເລິກ, save req.body ໂດຍກົງ ແລະ ສົ່ງ error ພາຍໃນອອກໄປຫາຜູ້ໃຊ້ ເປັນທັງບັນຫາຄວາມສະອາດ ແລະ ຄວາມປອດໄພ. Validate ແບບ fail-fast, whitelist field ແລະ ສົ່ງ error ໃຫ້ handler ກາງຈັດການ.

ReadabilitySecurity
ກ່ອນປັບ — ແບບທຳມະດາ
1app.post('/api/users', async (req, res) => {
2 if (req.body.email) {
3 if (req.body.password) {
4 if (req.body.password.length >= 8) {
5 try {
6 // saves ANY field the client sends
7 const user = await User.create(req.body)
8 res.json(user) // leaks password hash
9 } catch (e) {
10 // leaks internal details to the client
11 res.status(500).json({ error: e.message })
12 }
13 } else {
14 res.status(400).json({ error: 'Password too short' })
15 }
16 } else {
17 res.status(400).json({ error: 'Password required' })
18 }
19 } else {
20 res.status(400).json({ error: 'Email required' })
21 }
22})
ຫຼັງປັບ — Clean & Fast
1app.post('/api/users', async (req, res, next) => {
2 try {
3 const { email, password } = req.body
4
5 // guard clauses: fail fast, no nesting
6 if (!email) return res.status(400).json({ error: 'Email required' })
7 if (!password || password.length < 8)
8 return res.status(400).json({ error: 'Password must be 8+ chars' })
9
10 // whitelist fields - never save req.body directly
11 const user = await User.create({ email, password })
12 const { password: _, ...safeUser } = user.toObject()
13
14 res.status(201).json(safeUser)
15 } catch (err) {
16 next(err) // central error handler logs & hides internals
17 }
18})
05

Cache ຜົນລັບທີ່ຄິດໄລ່ໜັກດ້ວຍ Redis

Aggregation ໜັກໆທີ່ຜົນລັບເກືອບບໍ່ປ່ຽນ ບໍ່ຄວນ run ໃໝ່ທຸກ request. Cache ໄວ້ 5 ນາທີ ຫຼຸດເວລາຕອບຈາກຫຼາຍຮ້ອຍ ms ເຫຼືອປະມານ 1ms ແລະ ຫຼຸດພາລະຖານຂໍ້ມູນລົງມະຫາສານ.

PerformanceRedis
ກ່ອນປັບ — ແບບທຳມະດາ
1app.get('/api/stats', async (req, res) => {
2 // heavy aggregation runs on EVERY request
3 // even though the result barely changes
4 const stats = await Order.aggregate([
5 { $group: { _id: '$productId', total: { $sum: '$amount' } } },
6 { $sort: { total: -1 } },
7 { $limit: 10 },
8 ])
9
10 res.json(stats)
11})
ຫຼັງປັບ — Clean & Fast
1const CACHE_KEY = 'stats:top-products'
2const TTL_SECONDS = 300 // fresh enough for a dashboard
3
4app.get('/api/stats', async (req, res) => {
5 const cached = await redis.get(CACHE_KEY)
6 if (cached) return res.json(JSON.parse(cached)) // ~1ms
7
8 const stats = await Order.aggregate([
9 { $group: { _id: '$productId', total: { $sum: '$amount' } } },
10 { $sort: { total: -1 } },
11 { $limit: 10 },
12 ])
13
14 await redis.set(CACHE_KEY, JSON.stringify(stats), 'EX', TTL_SECONDS)
15 res.json(stats)
16})

As a translator between computers & humans

© 2026 - All rights reserved