🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

前言:準備好的一天永遠不會到來

準備好的一天永遠不會到來。
當機會來到眼前時,首先要抓住它。
知識和技術可以在抓住後再獲得。這樣就沒問題了。

在這個觀念形成之前,2025年在各種意義上都是“動盪”的。
我休職、自我投資、改變環境、轉職,甚至改變居住地。


Episode 0:前一年四月的調動,所有的一切都是前兆

因為公司的原因而調動了。
在此時我還是覺得「努力應該會有辦法」,但回首過去,感覺從這裡開始漸漸出現了些微的偏差。


Episode 1:10月至12月,關係網絡上一直消耗著我

從10月到12月,我在部門的人際關係中苦惱不已。
年末年始的假期,甚至不是在休息,而是過度思考。

然後迎來了工作的開始。


Episode 2:2025年1月6日,在上班前轉身回去了

2025年1月6日,工作開始的那一天,雖然走到了職場附近,但怎麼也走不出一步,於是轉身回去了。
那之後我直接去了精神科,被診斷為適應障礙

診斷結果得出的那一瞬間,感受到的並不是安心,而是恐懼
「接下來會怎麼樣呢?」
「如果職業停滯該怎麼辦?」
未來瞬間變得一片空白的感覺。

從那時開始只上班了一周,並在1月13日請了假

(順便說一句,當時我已經擁有第三種電氣主任技術者(電檢三種)的資格。
但即便如此,狀況卻沒有發生改變。努力卻得不到回報的感覺,實際上是消耗我的心靈的。)

學習①:在到達極限之前逃跑的勇氣

這個經驗讓我學到了三點。

  • 在感到極限之前,擁有逃離環境的勇氣
  • 請假是勞動者的權利,使用它並不是羞恥
  • 也可以把它作為進入下一個階段的準備期間使用

Episode 3:2月,放棄自學一年半,進行自我投資

其實,我從一年半前開始自學編程
但即使學習,也看不到「能賺到錢的未來」。自己對於未來的路徑一直感到不安。
一起學習的夥伴也很少。

所以我下定決心。
「這樣回去沒什麼改變,唯有改變環境。」

於是2月,我進行了編程學校的自我投資。

學習②:自學是低效的,學校則是高速的

如果用出行來比喻的話,

  • 自學:像是慢慢走在國道上(雖然前進了,但也很遙遠而且容易迷路)
  • 學校:像是開高架公路朝著目的地前進(可重複性強,迷茫減少)

最有效的還是路線圖夥伴
「下步該做什麼」變得清晰,不安減少,當有夥伴一起同行時持續進步的難度下降。


Episode 4:4月,進入更高水平的社群

第一個學校很好。
但我更想要。

  • 更高水平的社群
  • 轉職後也能學習的路線圖
  • 社交媒體發信的技能

在社交媒體上,看到了同樣使用一個哈希標籤發信的“熱情團體”(名字不便透露)。
那裡有許多比自己更強的人,我想「想要在這個環境中挑戰」。

學習③:擁有比自己強的人很多的環境,絕對會成長

  • 如果身邊有許多比自己強的人,技能將得到提升
  • 基準提高的話,學習的無效性減少
  • 獲得能繼續學習的設計後,目標會從「轉職」變成「提高價值的工程師」

Episode 5:8月,轉職和居住地變更。為了變強而改變“場所”

8月,我在東京的一家初創公司擔任網頁工程師,並改變了居住地。

我想在現代的環境中累積實務經驗。
我知道強大的工程師會聚集在都市。
在工程師基數多的地方進行交流,建立人脈,是我所希望的。

在轉職後的幾個月裡,我從前端→後端→全端開發,目前參與AI代理人的開發。

學習④:將風險進行評估後再跳進去。機會稍縱即逝

  • 改變居住地是可怕的。但如果能夠評估風險容忍度而跳進去,就會有超乎想像的回報
  • 準備好是根本不可能的。機會來了就要抓住
  • 想要能進行開發,就需要身處能快速開發的環境中,別無例外

Episode 6:轉職後(前端)所做的事情

※ 接下來將集中講述我在轉職後負責的業務中的 前端實現


6-1. 首先積累“小的UI修正”

實務中我們最初所接觸的,很多是所謂的小改進。

  • 按鈕等元素的微調(邊距、佈局、可視性)
  • 將顯示崩潰的地方修正為正確的UI
  • 將“未顯示為表格”的地方整理為可見的表格形式

這些小的修正看似不起眼,但卻與產品的可信度直接相關。
更重要的是,這是了解現有代碼的癖好、團隊的寫作風格、組件思想的最佳方式。


6-2. 開始進行最重要的頁面(首頁)的重構

我專注於首頁的重構

最糟糕的是 邏輯與顯示的混合(膨脹)
首頁的代碼達到了 500~1000行,數據的獲取、整形、狀態、分支和顯示全部寫在同一個文件裡。

在這種狀態下,每當增加新功能時

  • 不知道該去觸碰哪裡
  • 影響範圍無法預測
  • 增加需要“順便修正”的點,造成無法管理

結果,首頁變成了「不敢觸碰的頁面」。


6-3. 重構中最重要的是“順序”

本次重構中,最重要的是優先考慮 順序 而非技術選擇。

結論是這樣。

先分割顯示用的組件,再考慮分割邏輯。

如果先從邏輯開始切分,會在未看清UI整體性的情況下開始分割,
造成分割粒度不穩定,責任重疊,最終不能提高可讀性。

相反的,若先在「有意義的單位」上進行顯示分割,

  • 畫面的結構在代碼上會一目了然
  • 需要哪些數據顯示在哪個UI上會變得清晰
  • 在哪裡持有哪些狀態的判斷會更容易

依據這個順序,自然可以進入邏輯整理的階段。


6-4. 設計時特別注意的事項(徹底遵守C/P模式)

首頁使用了Container / Presentation模式進行重構。
這次的方針是 按頁面來統一目錄,使責任只需靠置放位置來判斷。

首先,重構後的目錄結構如下。

features/
  home/
    containers/
      HomeContainer.tsx
    presentations/
      HomePresentation.tsx
      components/
        HeroSection.tsx
        KpiSummary.tsx
        RecentUpdatesTable.tsx
    hooks/
      useHome.ts        # ※只有在需要時(不進行過度切割)
    api/
      homeApi.ts        # ※非必要(API調用的責任)

這樣的結構,使得即使是初見之人也能明確辨識:

  • 「邏輯在 containers」
  • 「顯示在 presentations」
  • 「UI的整體性在 presentations/components」

接下來,C/P的關係(呼叫方向)也固定為 單向

✅ 這裡的關鍵規則:C → P 只有這一種
❌ 不構建 P呼叫P / C呼叫C 的結構
(因為這樣會使邊界模糊,會減少分割的意義)

在這一前提下,我具體遵守的規則如下。

① 顯示組件根據UI建立(按意義單位進行適當切割)

「是否能切割」不是關鍵,必須按照UI的整體性來進行。
與用戶作為一個整體能識別的單位相適應,將增強對變更的抵抗力。

② 不打亂C/P的排列順序(C→P→C→P是OK,C→P→P和C→C→P是NG)

  • 僅限C→P的關係
  • 不構建P呼叫P / C呼叫C 的形式

原因很簡單,這樣的話便會導致失去分割的意義
邊界變得模糊之後,又會回到“混雜”的狀態。

③ 切割為hook時要謹慎(不隨意切割)

hook雖然很方便,但切割過度會導致責任分散,難以追蹤。
只有在「可重用且責任明確」的情況下才切割。

④ 目錄結構遵守C/P模式

在置放位置上使責任可見。
邏輯放在containers,顯示放在presentations。消除迷惑。

⑤ 狀態管理盡可能由“使用方”管理(原則上不由邏輯方持有)

如果過多地將狀態集合在上方,Container將膨脹成“神組件”。
因此,狀態應盡量靠近使用方的組件
使Container專注於整個頁面的控制。


6-5. 成果:新增功能變得更容易(可擴展性)

重構後最大的改善並非外觀,而是 可擴展性

  • 當增加顯示時:哪個顯示組件將增加清晰
  • 當增加數據時:只需在Container側獲取並傳遞即可
  • 當增加狀態時:由於靠近使用方,影響範圍小

結果,當新增需求來臨時,
我能夠感受到「不會破壞整個頁面就能嵌入」的感覺。


6-6. 為重構加上測試(……其實本來想要先做的)

由於首頁的重構大幅改變結構,「雖然外觀相同卻可能會崩潰」的風險很高。
然而當時,重構前並沒有準備足夠的測試。

結果,自己做的確認相當粗糙。

  • 打開畫面
  • 點擊
  • 輸入
  • 一一查看預期導線

……這些過程都在地道的重複。
但手工操作難免會出現漏網之魚。

換句話說,說得不好聽「在沒有任何保障的狀態下進行」
回想起來,這真的相當可怕。

引入了一個可以“攔截”HTTP的測試庫

因此,從某個時候開始,導入了一個可以攔截HTTP請求並替換回應的測試庫。
(例如:MSW(Mock Service Worker)的機制)

目標不是在「內部實現」上,而是在用戶視點下保證行為。

import { http, HttpResponse } from "msw";

export const handlers = [
  http.get("/api/home", () =>
    HttpResponse.json({
      kpis: [{ label: "Todo", value: 3 }],
      recentItems: [{ id: "1", name: "Item A", status: "open", updatedAt: "2026-01-05" }],
    })
  ),
];

6-7. 失敗:將應該在後端撰寫的測試寫到了前端,範圍擴大過度

這明顯是一個失敗。

本應在後端維護的「規格的正確性」與「領域規則」,卻試圖在前端中維持,最終範圍擴大了太多。

  • 測試觀點增多
  • 模擬變得複雜
  • 增加“順便修正”的項目
  • PR的差異變得龐大

學習:前端的測試應該聚焦於“畫面的行為”

現在我將角色分工進行如下劃分。

  • 後端:領域規則・規格保證(正確性)
  • 前端:顯示・導線・輸入・狀態遷移(行為)

6-8. 其實「寫完測試後再重構」是正確的(明白TDD的重要性)

此次過程中我最深刻的感受就是這一點。

在重構之前,期望有一個先確保“未損壞”的測試。

當時幾乎沒有測試,我乖乖地用手工去操作和確認每一個步驟。
但手工操作仍然會出現漏網之魚。

也就是說,有著 「在沒有任何保障的恐懼」 的情況下進行。
回頭想想,真的相當可怕。

反過來說,如果可以像TDD(測試驅動開發)一樣,預先固定行為(期望值)
那麼該做的事情就會變得明確。

  • 「這個功能需要滿足什麼條件才算OK」會提前決定
  • 實現過程中可以隨意更改(結構可以自由變動)
  • 然後對變更會更加自信

結果,也會更容易做出大膽的結構改變。
如果再做同等規模的改修,首先要建立最低限度的行為測試(重要導線),然後再逐步進行重構。
這是我個人得出的結論。


Episode 9:案例研究(全端)針對每個學校 / 每個班級輸出學員CSV的功能

接下來,我將對我所全端實現的功能進行深入探討,作為案例研究。

實現的是 針對每個學校 / 每個班級的學員資訊輸出CSV的功能

有三個要點。

  • 由於UI不同,實現了兩個端點
  • 權限是通過 Pundit(僅Policy) 確保「僅屬於所屬學校的資料可以訪問」
  • 商業邏輯隱藏在 Service內部,保持Controller的輕量化

此外一個重要的規範是,無論哪個UI都可以「不加篩選=契約的所有學員輸出」


9-1. 要求整理:有2種UI → API也分成2條(為了不迷路而進行的設計)

這次CSV的輸出,外觀有兩種模式。

  • 學校別CSV:以學校為起點進行過濾,輸出CSV的UI
  • 班級別CSV:以班級為起點進行過濾,輸出CSV的UI

在這裡,重要的要求是無論哪個UI都能夠「不加篩選=全件輸出」

  • 學校未指定 → 輸出所有契約的學員
  • 班級未指定 → 輸出所有契約的學員
  • 有指定 → 根據指定條件進行篩選輸出

由於UI不相同,若硬要將其歸於一個API,
會增加查詢和分支,最終提高理解成本。

因此我決定了。

端點分成兩條,根據UI分開API。

學習:在龐大的代碼基礎上,「分開的勇氣」將成為實現速度的關鍵

比起“保持共通的優雅”,
優先考慮「不迷惘」「可追蹤」「可審核」會更快。


9-2. 認可(Pundit):通過Policy固定只允許所屬學校的授權

這個功能由於CSV的性質,信息洩露風險較高
所以單靠認證(登錄)是不夠的,必須進行授權。

規範如下。

  • 僅能輸出自己所屬學校的資料
  • 其他資料不能輸出

在實現中僅建立Pundit的Policy
避免在Controller中的隨意if判斷。

學習:Pundit的價值在於“權限規範”作為代碼的存在

  • 「誰能」「做什麼」的定義會集中在一個地方
  • 在審核時便於解釋
  • 規範變更後容易追蹤

9-3. Rails:保持Controller的輕量化,將商業邏輯隱藏在Service中

在全端實現過程中,我深刻感受到這一點。

一旦在Controller中放入邏輯,維護工作就結束了。

CSV輸出容易增加要求(添加列、條件、特例處理)。
因此,我採取了不增加Controller負擔的方針。

自己的責任分割如下。

  • Controller:接收輸入 / 進行授權 / 回傳(輕量化)
  • Service:隱藏商業邏輯(對象篩選・CSV生成・檔名等)
  • Query(必要時):提取CSV所需的基本資料(最小限度)

這次的輸出條件,最終整理為以下幾點。

  1. 無篩選 → 輸出所有契約的學員(※無論是學校別 / 班級別都可以)
  2. 學校指定 → 在所屬學校內進行篩選輸出(由Pundit擔保)
  3. 班級指定 → 在所屬學校內的班級進行篩選輸出(由Pundit擔保)

將「全件」與「篩選」歸於同一個層面後,Service側負擔了進行控制,
使得Controller側保持輕量化。

學習:將邏輯歸納到Service中,會使審核和修正的速度提高

  • Controller輕量化=差異易於理解
  • 邏輯的置放位置明確=不會迷失
  • 測試也更容易圍繞Service撰寫

9-4. 前端(React):通過API hook連接,分開UI與責任

在前端,我做了兩大部分。

  • 創建API hook 與後端連接
  • 實現UI(學校別 / 班級別)

不過,這裡也有一些反省。

反省:狀態過多集中在Container

選擇狀態和loading等太多持有在Container中,
導致Container膨脹,責任混雜。

現在我會這樣劃分。

  • hook:僅處理通信和結果(CSV Blob或回應)
  • UI:選擇狀態(學校/班級)和按鈕控制
  • Container:僅進行頁面控制(必要最小限)

學習:狀態由“使用方”管理,通信由hook處理,控制則由Container負責

僅遵守這一規則後,實現後的修正一下變得輕鬆了許多。


9-5. CSV規範:文字編碼統一為UTF-8,檔名要“安全化”以避免問題

文字編碼我將其統一為 UTF-8

但在實務中,更容易遇到的問題是檔名而非內文。

  • 檔名需為「日期+學校名稱」
  • 但如包含日文或符號,根據環境容易出現文字錯亂或崩壞

因此我採用的是,

檔名通過編碼/安全化(移除符號)確保安全形態

「用戶所需的意義(可辨識學校)」雖然保留,
但卻排除那些容易崩壞的字符。

(實作備註)安全化範例(擬似)

# 例:移除符號+空白替換為_(擬似)
safe_school = sanitize_filename(school_name) # 移除符號/連續空白替換為_/長度限制等
filename    = "#{Date.today.strftime('%Y%m%d')}_#{safe_school}.csv"

sanitize_filename 是「僅保留英數字・連字符・底線」「將空白替換為_」等,
“去除容易崩壞的字符以減少事故”的目的。

補充:相對於完全保留學校名稱,
「學校名稱(安全化)+學校ID」的形式會增添“一致性”,運營時也不容易出現問題。

學習:CSV的完成要包含「生成」與「使用方式」

  • 文字編碼
  • 檔名
  • 下載時的用戶體驗(loading/失敗顯示)

做到這些,才算是“業務功能”的完成。


9-6. 反思 → 學習(這次實現中的深刻體會)

  • 不清楚Pundit
    → 本質在於「聚合權限規範到Policy中」而非「寫法」

  • 不清楚前端與後端如何連接
    → 事實是API的約定(端點/輸入/輸出/權限)

  • 代碼基礎太龐大而迷失方向
    → 在擬定「此次觸及範圍」後,可減少迷失

  • 完全推給AI,結果卻不會解釋
    → 比起速度,更需要「能在審核時解釋的理解」

  • PR的概要無法反映意圖
    → 雖然代碼可讀,但意圖不寫就不會傳達。


9-7. 結論:全端的成長不在於“實現力”而在於“設計的判斷軸”

在這次完整的功能實現中,最大的成長在於以下幾點。

  • 按UI分開兩條API的判斷
  • 通過Pundit Policy固定所屬範圍的判斷
  • 保持Controller的輕量,將邏輯聚集在Service中的判斷
  • 通過安全化解決檔名的文字問題的判斷

在「動作」的基礎上,應將重心轉向「可運行性」。
這樣設計出的判斷,在實務中將會是最有價值的。


補充:關於AI代理人的開發,將在下次的文章中寫到

目前我也參與了AI代理人的開發,但這超出了本文章的主題(2025年的環境變化與實務的學習),因此不再深入。

但這無疑將成為我下一個重大挑戰。
如果有機會,我將會將「在AI代理人開發中所學」另行整理成文章。


結尾:致2025年1月6日的自己

親愛的,致2025年1月6日的我。

首先想告訴你。
今天是你最年輕的一天。
要開始的不是明天,也不是一小時後,而是現在開始。
……但是,這樣的話你未必會覺得「那也沒辦法」。我懂。

我仍然清晰地記得那天早上的事。
在冰冷的寒風中,穿著西裝,握著冷冰冰的自行車手把,跑到了公司的附近。
一邊想著「可以的,需要去」,但身體的深處又在拒絕的感覺。
明明已經走到了工作的附近,卻怎麼也走不出一步,於是轉身回去了。

那之後我去精神科,被告知了適應障礙。
當得到這個名字的瞬間,感受到的比起安心,更是恐懼
「接下來會怎麼樣」
「如果職業停滯會怎麼辦」
未來瞬間變得一片空白的感覺。

但是,我要先告訴你結論。

轉身回去是正確的。
並不是逃避,而是在崩潰之前停止了。
那是一次為了邁向下一步的明智選擇。

休職的事,現在我則能自信地說出來。
這並不是羞恥,而是權利。
而這段時間,不僅僅是「休息的時間」,更是改變環境的時間

接下來,給你簡單預告一下。
你將會在隨後放棄自學,進行自我投資。
進入編程學校,遇見講師、導師、以及夥伴。

在那裡,出現了改變的瞬間。
「我無論如何也不會是一個人」的感覺,
不再只是希望,變成了無可置疑的自信,最終變為確信的瞬間。

這會讓你變得不一樣。
多次突破舒適圈。
持續給自己施加「適當的壓力」。
依靠眼前的機會,堅信著這是一個「機會」,毫不含糊地抓住。
轉職後也會貪婪地追求「自己想做的事情」,真正地做到。

還有,這是我的真心話。
關於一年半的自學期間,心中留有一絲悔恨。
「如果能早些投入自我投資,或許能更早以工程師身份啟程」。
但与此同时,自學培養的自我驅動力,確實在目前的工作中得到了體現。
提升人材價值的經驗之一毫無疑問。
但這條路——並不是推薦給每一個人的道。
你只是剛好堅持下來而已。

最後,只想留下這段話。

感謝2025年遇見的所有人。
特別是閱讀到這裡,覺得「這或許是我」或是「這或許是我」的人。
衷心感謝你們在我們的距離中和我互動,無論是我的任性還是稚嫩,都能包容我。
尤其是轉職的夥伴,我知道,真心覺得你們很厲害。
雖然這樣說不好,但卻真實存在。
但你們仍然接受我,為我提供了空間,然後培養我,真的非常感謝。

……好了,就要收尾了。

致一年前的自己。
如果此刻你在顫抖著覺得「無法回去」「無法挽回」,那我告訴你。
顫抖是可以的,害怕也可以。
但別就在這裡停下來。

今天是你最年輕的一天。
開始的不是明天,而是現在。

而正在閱讀這篇文章,正考慮挑戰的你們。
準備好的一天永遠不會到來。
如果期待不安消失後再行動,那一輩子都會是如此。
所以,選擇。

改變環境。
抓住機會。
抓住之後再學習。

沒問題。不需要完美。
就算笨拙,走出來的人會贏。

敬上。


後記:在本文最後再說一次

準備好的一天永遠不會到來。
當機會來到眼前時,首先要抓住它。
知識和技術可以在抓住後再獲得。這樣就沒問題了。

讀到這裡的你,也許已經隱約意識到了。
「想要改變」「想要挑戰」「但又害怕」。

但接下來成功的,或許就是——你。
結果只有行動過的人才能知道。
光是考慮的世界,什麼事情都不會發生。真的。

再說一遍。
今天是你最年輕的一天!!
讓我們一起挑戰吧。

那麼有朝一日希望能與你享受喝酒的時光。
我其實也很想和你一起喝酒!!(笑)

未來一定會光明!!

以上,就是未來的強強工程師想對大家傳達的一切。


下一步 (2026年)

2026年,這一年將會是 靠實力讓別人信服的年

要做的事情只有三件。

  • 將AI代理人開發變為“能說的實現”(設計→實現→運營)
  • 先鋪設測試,然後才能用以確保功能的自我實現(最低限度的主要導線即可)
  • 將所學的事情、所經歷的苦難,以及所做的成功,寫成文章,為全世界提供價值
    (不僅止於「自己的學習」。盡量幫助到下次遇到類似困難的任何人)

下次文章中,我會全方位揭露目前參與的 AI代理人開發的真實情況
不追求“看起來厲害”,而是能夠重現的形式


原文出處:https://qiita.com/Keita-0025/items/c0fb3bed10f49b0ed523


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝6   💬3   ❤️2
141
🥈
我愛JS
💬1  
6
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付