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

Sun PochinSun Pochin
9 min read

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 component

  • deleteList() – 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 pages

  • useApi() – API call logic, used everywhere

  • useLocalStorage() – 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() in useListActions.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 on boardStore.

🎯 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 HTML

  • Modify TrelloBoard.vue event handlers

  • Add 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() - 本地儲存,各種組件都可能用到

🎓 結論:

我這次可能犯了「為了用新技術而用」 的錯誤!

更好的做法可能是:

  1. 把函數直接寫在 ListItem.vue 裡

  2. 等真的有其他地方需要用到時,再抽成 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,只要:

  1. 在 useListActions.ts 加上 copyList() ✅

  2. 在 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):

「透過『插座』連接,不要直接接線」

  • 就像手機充電線,不管什麼牌子的手 機都能用同一個插座

這樣程式碼就更「有彈性」,以後改東 西會輕鬆很多! 😊

1
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.