Claude refactored codes to fit SOLID and explained like I'm 10 years old

English version (中文版在下面,Claude 幫我 refactor 成 SOLID 原則並且用十歲孩童方式講解給我聽)
The reason this started was because I felt that TrelloBoard.vue
was way too long — about 200 lines. (Yes… right now even 200 lines feels too long for me. It’s funny because back then I had to deal with 10,000-line classes, with pointers inside them!)
Claude Pro kindly helped me split things up, and I casually asked if this followed the SOLID software engineering principles. That’s when Professor Claude showed up shining brightly!
👉 Repo: https://github.com/sunpochin/trilou
Honest analysis of composables 😊
🔍 Let’s first see what I did:
I moved the following functions that were originally inside TrelloBoard.vue
:
// originally inside the component
const addNewCard = (listId) => { /* ... */ }
const deleteList = (listId) => { /* ... */ }
const addNewList = () => { /* ... */ }
Into a composable called useListActions.ts
:
// now as a composable
export const useListActions = () => {
return { addCard, deleteList, addList }
}
🤔 Was this a good idea?
❌ Honestly, this might have been overengineering.
Explaining it like a 10-year-old could understand
🏠 Using a storage box analogy:
📦 When do you need composables (storage boxes)?
When you have the same thing that needs to be used in different places.
Examples:
Your pencil is needed in both the study room and the living room for drawing.
Your toy car is played with both in your bedroom and in the living room.
→ In this case, having a portable storage box is very useful.
🚫 When do you NOT need composables?
When your stuff is only used in one place.
Examples:
Your bed only stays in the bedroom.
Your fridge only stays in the kitchen.
→ No need to make it portable.
🎯 My mistake this time:
❌ There wasn’t any real reuse need.
addCard()
– only used by the list componentdeleteList()
– only used by the list component
✅ A simpler approach could have been:
Just write these functions directly inside ListItem.vue
!
🏆 When are composables really useful?
👍 Real examples:
useAuth()
– login/logout logic, needed on many pagesuseApi()
– API call logic, used everywhereuseLocalStorage()
– local storage, needed in many components
🎓 Conclusion:
This time I may have made the mistake of using a new tech just for the sake of using it!
A better approach might be:
Write the functions directly in
ListItem.vue
.When they’re truly needed elsewhere, then extract them into a composable.
Follow the YAGNI principle: You Aren’t Gonna Need It (yet).
📝 Lesson learned:
Don’t split things apart just to look “professional.”
First make the program work, then optimize.
Refactor only with a clear reason — not just for the sake of refactoring.
👏 Your doubt was correct! Sometimes simpler is better.
Did the refactor align with SOLID?
⏺ Great question! Let’s check which SOLID principles this refactor matched 🤔
🔍 SOLID Principles Check
✅ S – Single Responsibility Principle
This was achieved!
Originally, TrelloBoard``.vue
was doing way too much:
Managing the whole board layout ❌
Handling list drag-and-drop ❌
Managing list menus ❌
Handling card actions ❌
Managing modals ❌
After refactoring, responsibilities are separated:
TrelloBoa``rd.vue
→ handles board layout + coordination ✅ListItem``.``vue
→ handles a single list’s rendering ✅ListMenu.vue
→ handles menu show/hide ✅useListActions.ts
→ handles list-related business logic ✅
✅ O – Open/Closed Principle
Also achieved!
Now if I want to add a new feature (e.g., copy a list):
I don’t have to modify existing
ListMenu.vue
logic.Just add
copyList()
inuseListActions.ts
.Add a new button in
ListMenu.vue
.
→ No risk of breaking existing add/delete features! ✅
🤷 L – Liskov Substitution Principle
Not really relevant here.
We’re not using inheritance, only composition.
❌ I – Interface Segregation Principle
Not really shown.
Vue’s props
and emits
already act like interfaces.
✅ D – Dependency Inversion Principle
Partially achieved!
Improvement:
Components don’t directly depend on
boardStore
.Instead, they go through
useListActio``ns()
.
Limitation:
- Inside
useListActions
, it still depends directly onboardStore
.
🎯 Summary:
Clearly improved:
✅ S (Single Responsibility) – the biggest win!
✅ O (Open/Closed) – easier to extend features
✅ D (Dependency Inversion) – partially improved
Less relevant:
🤷 L (Liskov Substitution) – no inheritance here
❌ I (Interface Segregation) – Vue already covers this
🏆 Biggest takeaway:
Mainly the improvement in S (Single Responsibility)!
👶 Explaining it like a child:
Before → one person had to do all the chores (washing dishes, sweeping, laundry).
Now → each person does just one chore.
Everyone can do their part much better!
So yes, maybe a bit “overengineered,” but it did make code responsibilities clearer 😊
Can we talk more about O & D?
You asked: can we expand more on Open/Closed and Dependency Inversion, explained like to a 10-year-old? Sure!
🚪 O – Open/Closed Principle
🎮 Game console analogy:
❌ Before (bad):
Like a game console with games hard-wired inside.
- If you wanted a new game, you had to open up the console and change its circuits.
✅ After (good):
Like a console with cartridges.
Want a new game? Just insert a cartridge.
No need to break existing games.
🧩 Practical Example:
Adding “copy list” feature
❌ Before:
Modify
TrelloBoard.vue
menu HTMLModify
TrelloBoard.vue
event handlersAdd a new function directly inside
TrelloBoard.vue
Super easy to break existing code 😱
✅ After:
Just add one button in
ListMenu.vue
Just add one function in
useListActions.ts
Just handle it in
Lis``tItem.vue
→ No need to touch existing “add card” or “delete list” logic at all!
🔌 D – Dependency Inversion Principle
🏠 Electrical socket analogy:
❌ Before (bad):
Appliances were soldered directly to the wall wires.
If you moved house, you’d have to rewire everything 😱
✅ After (good):
Appliances plug into a socket.
Move house? Just use the same plug. No rewiring needed!
🧩 Practical Example:
Before:
const boardStore = useBoardStore()
boardStore.addCard(listId, title) // direct call!
→ If storage changes (say, move from Supabase to localStorage), you’d have to fix everywhere.
After:
const { addCard } = useListActions()
addCard(listId, title) // doesn’t care how it works internally
→ If storage changes, you only edit one file (us``eListActions.ts
).
→ All components remain untouched!
🏆 Quick summary for kids:
🚪 Open/Closed → “Add new blocks without breaking old ones.” Like Lego.
🔌 Dependency Inversion → “Use sockets, not hard wiring.” Like charging any phone with the same outlet.
Both make your code more flexible and easier to change later 😊
中文版
起因是我覺得這個 trelloBoard.vue 太長了,有兩百行。(哎,是的,現在兩百行對我來說就算太長,真神奇以前要面對一萬行的 class, 裡面還有 pointer.)
Claude pro 很好心的幫我拆開,我順手問了這是否符合 SOLID 這個 software engineering principles, 然後 Claude 老師就閃亮登場了!
https://github.com/sunpochin/trilou
很好的問題!讓我誠實分析一下 composables 的使用 😊
🔍 先看看我做了什麼:
我把原本在 TrelloBoard.vue 裡的這些函數: // 原本直接寫在組件裡 const addNewCard = (listId) => { /* ... / } const deleteList = (listId) => { / ... / } const add**NewList = () => { / ... */ }
搬到 useListActions.ts composable 裡: // 現在變成 composable export const useListActions = () => { return { addCard, deleteList, addList } }
🤔 這樣做好嗎?
❌ 老實說,這次可能是「過度設計」了
讓我用小朋友能懂的方式解釋:
🏠 用收納盒比喻:
📦 什麼時候需要 composables(收納盒)?
當你有「相同的東西」要放在「不同地方」時
例如:
你的鉛筆既要放在書房,也要放在客 廳畫畫
你的玩具車既要在臥室玩,也要在客 廳玩
→ 這時候準備一個「可攜帶的收納盒 」很有用
🚫 什麼時候不需要 composables?
當你的東西只在「一個地方」用時
例如:
床只放在臥室
冰箱只放在廚房
→ 不需要特別做成「可攜帶的」
🎯 我這次的問題:
❌ 沒有真正的重複使用需求
// 這些函數只在列表相關的地方用到 // 不會在其他完全不同的地方使用 addCard() - 只有列表組件會用 deleteList() - 只有列表組件會用
✅ 更簡單的做法可能是:
就直接把這些函數寫在 ListItem.vue 裡面!
🏆 什麼時候 composables 真的有用?
👍 真正適合的例子:
// 這種真的會在很多地方用到 useAuth() - 登入/登出邏輯,很多頁面都需要 useApi() - API 呼叫邏輯,到處都要用 useLocalStorage() - 本地儲存,各種組件都可能用到
🎓 結論:
我這次可能犯了「為了用新技術而用」 的錯誤!
更好的做法可能是:
把函數直接寫在 ListItem.vue 裡
等真的有其他地方需要用到時,再抽成 composable 3. 遵循「YAGNI 原則」(You Aren't Gonna Need It) - 你還不需要它
📝 學到的教訓:
不要為了「看起來專業」就過度拆分
先讓程式能跑,再考慮優化
重構要有明確的理由,不是為了重構 而重構
你的質疑是對的! 👏 有時候簡單直接的方法更好。
不過這次重構有符合 solid 原則的哪一點嗎?
⏺ 很棒的問題!讓我分析一下這次重構符 合了 SOLID 原則的哪些部分 🤔
🔍 SOLID 原則檢查:
✅ S - Single Responsibility Principle (單一職責原則)
這個有做到!
原本的問題:
// TrelloBoard.vue 原本做太多事情:
管理整個看板佈局 ❌
處理列表拖拉 ❌
管理列表選單 ❌
處理卡片操作 ❌
管理模態框 ❌
重構後的改善:
// 現在職責分離: TrelloBoard.vue → 只管看板佈局和整體協調 ✅ ListItem.vue → 只管單個列表的渲染 ✅ ListMenu.vue → 只管選單的顯示/隱藏 ✅ useListActions.ts → 只管列表相關的業務邏輯 ✅
✅ O - Open/Closed Principle (開放封閉原則)
這個也有做到!
現在如果要新增功能: // 想要新增「複製列表」功能? // 不用修改現有的 ListMenu.vue,只要:
在 useListActions.ts 加上 copyList() ✅
在 ListMenu.vue 加上新的按鈕 ✅ // 不會影響到現有的刪除/新增功能
🤷♂️ L - Liskov Substitution Principle (里氏替換原則)
這個...不太適用
因為我們沒有繼承關係,都是組合模式 ,所以這條原則在這裡不太相關。
❌ I - Interface Segregation Principle (介面隔離原則)
這個沒有特別體現
我們沒有定義很多介面,而且 Vue 組件的 props/emits 已經算是介面了。
✅ D - Dependency Inversion Principle (依賴反轉原則)
這個有部分做到!
改善的地方:
// 組件不直接依賴具體的實作,而是透過 composable ListItem.vue → useListActions() → boardStore // ↑ // 透過抽象層,不直接呼叫 store
但老實說,這個做得不夠徹底,因為 useListActions 內部還是直接依賴 boardStore。
🎯 總結:
✅ 明確符合的:
S (單一職責) - 這是最大的改善!
O (開放封閉) - 現在更容易擴展功能
D (依賴反轉) - 有一定程度的改善
🤷♂️ 不太相關的:
L (里氏替換) - 沒有繼承關係
I (介面隔離) - Vue 組件本身就有這個特性
🏆 最大的收穫:
主要是 S (Single Responsibility) 的改善!
用小朋友的話說:
原本一個人要做所有家事 (洗碗、掃地、洗衣服)
現在每個人專門做一件事
這樣每個人都可以把自己的事情做得 更好!
所以雖然可能有點「過度設計」,但確 實讓程式碼的職責分離更清楚了! 😊
\======
O (開放封閉) - 現在更容易擴展功能
D (依賴反轉) - 有一定程度的改善
可以多講講這兩點嗎?如果可能一樣用 十歲孩童法 ⎿ ⧉ Selected 1 lines from components/ListItem.vue in Visual Studio Code
⏺ 好的!讓我用十歲小朋友能懂的方式, 詳細解釋這 兩個原則 😊
🚪 O - Open/Closed Principle (開放封閉原則)
🎮 用遊戲機來比喻:
❌ 原本的做法 (沒有遵循原則):
// 就像一台「寫死」的遊戲機 // 原本的 TrelloBoard.vue 把所有功能都寫在一起 function handleListMenu(action) { if (action === 'add-card') { // 新增卡片的程式碼寫死在這裡 } if (action === 'delete-list') { // 刪除列表的程式碼寫死在這裡 } // 如果要新增「複製列表」功能... // 就要來這裡改這個函數!😱 }
問題: 每次要加新功能,都要「拆開 遊戲機」改裡面的程式碼
✅ 重構後的做法 (遵循原則):
// 就像一台可以「插卡匣」的遊戲機 // ListMenu.vue 現在像遊戲機,可以插不同功能
好處: 要加新功能,不用改原本的程 式碼,只要「插新卡匣」!
🧩 實際例子:
想要加「複製列表」功能?
❌ 原本要改很多地方:
// 要改 TrelloBoard.vue 的選單 HTML // 要改 TrelloBoard.vue 的 toggleListMenu 函數
// 要改 TrelloBoard.vue 的事件處理 // 要在 TrelloBoard.vue 加新的 copyList 函數 // 改來改去,很容易弄壞現有功能!😰
✅ 現在只要:
// 1. 在 ListMenu.vue 加一個按鈕 (不會影響其他按鈕) <button @click="emit('copy-list')"
複製列表
// 2. 在 useListActions.ts 加一個函數 (不會影響其他函數) const copyList = (listId) => { /* 複製邏輯 */ }
// 3. 在 ListItem.vue 接收事件 (不會影響其他事件) @copy-list="handleCopyList"
// 完全不用動原本的新增卡片、刪除 列表功能!😊
🔌 D - Dependency Inversion Principle (依賴反轉原則)
🏠 用插座來比喻:
❌ 原本的做法 (直接依賴):
// 就像電器直接「焊死」在牆上 // ListItem 直接使用 boardStore import { useBoardStore } from '@/stores/boardStore'
const boardStore = useBoardStore() boardStore.addCard(listId, title) // 直接呼叫!
問題:
如果以後想換成其他方式儲存資料 (比如 localStorage),就要改很多地方
就像如果要搬家,所有電器都要重新 接線!😱
✅ 重構後的做法 (透過抽象層):
// 就像電器透過「插座」連接電源 // ListItem 透過 useListActions 使用功能 import { useListActions } from '@/composables/useListActions'
const { addCard } = useListActions() addCard(listId, title) // 不知道底層怎麼實作,只知道「插上去 」就能用!
好處:
如果以後要換資料儲存方式,只要改 useListActions 就好
就像換插座裡的電線,電器完全不用 動!😊
🎯 實際例子:
想要把資料存到 localStorage 而不是 Supabase?
❌ 原本要改很多地方:
// 要找到所有用 boardStore.addCard 的地方 // 要找到所有用 boardStore.deleteList 的地方
// 要找到所有用 boardStore.addList 的地方 // 一個一個改成 localStorage 的寫法 // 很容易漏掉,而且超級麻煩!😰
✅ 現在只要改一個地方:
// 只要改 useListActions.ts 這一個檔案! export const useListActions = () => { // 原本: // const boardStore = useBoardStore() // return { addCard: boardStore.addCard }
// 現在: const addCard = (listId, title) => { localStorage.setItem('cards', /* 新的邏輯 */) } return { addCard } }
// 所有用到的組件完全不用改!😊
🏆 簡單總結:
🚪 開放封閉 (O):
「加新功能不用改舊程式碼」
- 就像積木,可以一直往上疊,不用拆 掉下面的
🔌 依賴反轉 (D):
「透過『插座』連接,不要直接接線」
- 就像手機充電線,不管什麼牌子的手 機都能用同一個插座
這樣程式碼就更「有彈性」,以後改東 西會輕鬆很多! 😊
Subscribe to my newsletter
Read articles from Sun Pochin directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Sun Pochin
Sun Pochin
I was the main web front-end engineer responsible for developing and maintaining the customer-facing platform for WisdomHall (品學堂) in 2024 Q3 Q4, code reviewed by CTO and a senior engineer. http://learning.wisdomhall.com.tw has total active 257k users, according to their Facebook post: https://www.facebook.com/share/p/1MnjbwZxCV/ My tech stack includes JavaScript, Vue 2, Nuxt 2, git, and Docker.