Skip to main content

高效學習法 CLT 學習負荷理論實踐 - 前端 Docker 學習

什麼是 CLT 學習負荷理論

若學習要事半功倍,就要「學習如何學習」,市面上有著許多優良好樹著墨學習方法,例如「大腦喜歡這樣學」、「超速學習」

但是本篇方法源自於教育心理學理論,是從 相见恨晚!我這輩子見過最好的學習方法 | CLT認知負荷理論 | 湯質看本質 這部影片中得知,我認為可以把學習過程庖丁解牛,拆解成最舒服的路徑

認知負荷理論的立論如下

  • 人類記憶分為短期記憶(工作記憶)和長期記憶
  • 短期記憶是稀缺資源(也就是我們的 RAM 很小)
  • 調用長期記憶比較不費力又省時
  • 在學習過程中減少短期記憶消耗,就能讓學習更加有效率

簡言之,我們不僅要努力學習,更應該要有意識地規劃學習的路徑和材料

元素交互性 - 知識點之間的關聯程度

首先,先談談認知負荷理論的「元素交互性」,元素交互性指的是知識點之間的交互關連程度

以 CSS 來說,font-sizemargin 彼此之間交互性不高,都可以作為獨立的知識點來學習,你不需要先懂 font-size 才能懂 margin

asyncawait 就是元素交互性很強的知識點了,你很難分開獨立地學習這兩個語法,並且還需要了解 Promise 的知識,以及和 .then() 方法的差異

這時候知識節點之間像是個彼此相依在一起的「知識圖譜」(graph)

學習三大原則

了解元素交互性的概念後,這個影片中將學習負荷理論化為三個主軸

  • 去除多餘:有意識地剪裁、挑選、篩選學習材料,區分雜訊跟資訊,抓大放小,不要過度糾結
    • 有個實驗是測試小孩摺紙作業的完成率,分為三組,為「圖片 + 文字指示」、「單純圖片指示」、「把指示印在摺紙上」,
    • 完成率分別是 「40%」、「60%」、「90%」,因為「圖片 + 文字指示」看似很詳細,但是創造了很多額外的認知負擔
    • reference: Cognitive load effects in a primary-school geometry task (Bobis et al., 1993)
  • 分割複雜:把學習分割任務,先學習元素交互性低的作為磚頭,再學習元素交互性強的,拿磚頭蓋房子
  • 交替實例:學了一塊觀念後,馬上接替做一個實例練習

這三點看起來是老生長談,但實踐後發現非常有用

接下來,將以一個前端工程師入門 Docker 作為實例,來看看這三個學習原則實際上如何運用

去除多餘 - 挑選學習材料 ,先只求動起來

若要起步學習新知識,第一直覺可能是到 udemy 尋找高評價課程,或在 Youtube 搜索教學影片

但是學習材料的差異,會顯著影響效率跟舒適度,怎麼說呢?

我們很有可能興沖沖買了課程,才發現內容是適用想成為 DevOps 的中高手,裡面課程對於 Docker 和 K8S 講得鉅細靡遺,或者在 Youtube 找到的影片教學,裡面範例是使用自己不熟悉的後端服務

在這樣的情境底下,很有可能學得跌跌撞撞、腦袋燒壞,被各種專有名詞轟炸,又看不到盡頭在哪裡,可是我們一開始的目標,只是想讓前端服務先能 dockerize 跑起來就好啊!

於是,我選擇了Net Ninja 的課程作為起手 - Docker Crash Course #1 - What is Docker? - YouTube,使用一個下午的時間進行學習,發現效果拔群

  • Net Ninja 是以 JS 為主的 Web Dev 講師,所以課程中範例的元素都是貼近自身背景,可以幾乎 0 耗損進行理解
  • Docker 的核心概念很明確:輕鬆重複部署、container 又輕又快,但是高度自由化的參數配置和指令,一開始會帶來很大的認知負擔,這堂課只求寫出一個簡單的 Dockerfile 跟 docker-compose,做出一個 MVP,於是也削減了許多學習負荷
  • 課程不長,適用快速建立觀念,又有實例搭配

分割複雜 - 了解元素交互性,庖丁解牛

若沒有進行知識點分割,很有可能被眾多專有名詞搞得頭昏眼慌,例如 Dockerfile、docker compose、volume、image、container、docker run、docker start、repository ...

這些東西彼此元素相依性高,於是我們要先抓重點,找出基礎且重要的知識點作為磚塊,例如:

  • Docker 類似虛擬機,但是不同之處在哪裡?優點是?
  • image 是什麼?
  • container 是什麼?

尤其搞懂 image 跟 container 的互動關係,對後面的學習無往不利,我們必須有意識地去觀察哪些是不斷被複用的關鍵知識點,哪些又是特定情境使用的進階知識,先放著以後再看也不遲

我們先來看看 docker 官網對於 image 的 定義與講解

A container image is a standardized package that includes all of the files, binaries, libraries, and configurations to run a container.

For a PostgreSQL image, that image will package the database binaries, config files, and other dependencies. For a Python web app, it'll include the Python runtime, your app code, and all of its dependencies.

There are two important principles of images:

Images are immutable. Once an image is created, it can't be modified. You can only make a new image or add changes on top of it.

Container images are composed of layers. Each layer represented a set of file system changes that add, remove, or modify files.

好的,對於一個基礎知識點,要處理這些定義就已花掉不少短期記憶跟認知資源了,難道我們複用的時候都要不斷記起這些東西嗎?

這時,我在研究與學習的過程中,會嘗試將這個東西 進行找尋類比、譬喻或是直覺意涵(雖然不總是完全精準對映),雖然花了點時間,但是值得一試,因為一旦找到了,會加速這個知識點進入長期記憶的過程

我們拿來類比的東西,都是從熟悉的生活經驗出發,在尋找類比的過程,我們像是拿既有的長期記憶,來重新理解新知識,這個關連一旦建立,就能降低認知負擔

(所以文章中的舉例都是從作者熟悉的事物出發,不一定適合你,這篇的舉例也是如此)

所以,我認為 image 或許...

  • 像是 switch 的卡帶,一旦被做出來了,就不能更動裡面的內容,read only
  • 如同卡帶一樣,裡面內含了運行遊戲的主要資源
  • 只有卡帶本身的話,不能玩遊戲,需要有程序去執行裡面的內容

所以,我認為 container 或許...

  • 像是 switch 插入卡帶後,被創造起來的遊戲程序
  • 這個程序會讀取卡帶的資源
  • 這個程序可以中止、重開、刪除
  • (如果沒有存檔)程序刪除並重起後,所有東西會消失,讀取卡帶重新開始

可是同一個 image 可以啟動數個 container 又彼此隔離,switch 一個遊戲不能多開 XD,switch 卡帶的類比在這時對映失敗(這也很正常),我們可以稍微重新擴展、轉換類比

  • image 像是 GBA 模擬器的 rom 檔
  • container 像是開起來的模擬器遊戲程序
  • 同一個 rom 檔可以重複多開好幾個模擬器程序,彼此獨立互不干擾
  • 模擬器程序可以被創建、中止、重開、刪除

好了,有了這些磚頭,就可以開始來蓋房子了

Dockerfile:打造自己的遊戲 ROM 檔

image 遊戲卡帶要從哪裡來?有些人已經有做好了,可以使用 docker pull 下載別人做好的 image

可是這終究是別人做的遊戲,我想自己做呀,不然怎麼把自己的前端應用打包成 image?

於是,我們需要 Dockerfile ,這就是生產自製卡帶的 SOP 流水線

既然如此可以理解到,裡面運行的指令(RUN)都是「製作卡帶」的過程,而非「運行遊戲」的過程

以下示範一個最簡單的 Dockerfile,把自己的 app 打包成 image

# 這是在前端 app 下的 dockerfile

FROM node:20.12.2

WORKDIR /app

COPY package.json ./
COPY package-lock.json ./
RUN npm install

COPY . .
  • FROM 從其他 image 作為基礎工具,因為我們需要 node.js 來啟動前端服務
  • 設定 build time 的 workdir 為 /app(會自動創建資料夾),避免在根目錄與其他東西打架
  • 把前端目錄的 package.jsonpackage-lock.json 搬到 workdir 底下,並進行 npm install
  • 把前端目錄下的所有 source code 搬到 workdir 底下 (會設 .dockerignore 原本目錄下的 node_modules 忽略掉)

於是這個 image 擁有了 node.js 環境,以及所需的 node_module 和 source code 可供 container 運行!

Volume:遊戲總是要保留存檔和資料

知道製作 image 的原理後,會發現兩個痛點

  • 在開發的過程,會是邊改程式碼邊看成果,難道每更動一次,就要重新創建一個新的 image ,來把新的程式碼複製進去嗎?(記得,image 是唯讀的)
  • container 刪除後,裡面所有東西都會不見,這樣 db 的資料怎麼辦?

這時候我們就要出動 volume 啦,最簡單的範例是,把 host 主機特定資料夾和 container 特定路徑 進行同步關連,例如 docker run 中的 -v $(pwd):/app

意思是,把當前 host 目錄掛載到 container 的 /app 底下,所以 host 資料夾內有什麼,container 的 /app 底下就有什麼

並且,這過程都是雙向同步更動

  • 我們在本機 host 資料夾新增一個檔案,container 的 /app 也會同步新增
  • 我們使用 container 在 /app 底下刪除某一個檔案,本機 host 資料夾的該檔案也會同步被刪除

我們可以想像,資源不能只靠遊戲卡帶,可能會下載一些 PATCH 檔或更新資訊在遊戲主機某個資料夾,而遊戲程序本身也有權限新增存檔在那個 host 資料夾,也可以從 host 資料夾抓取資源

Docker Compose:參數化一鍵多開模擬器

好了,接著又開始出現痛點

  • 假設有一個全端專案,使用 Express、MongoDB 、React,總不能每次都要手動 docker run 來啟動三個 container 來形成完整的服務
  • 就算只需要一個 container,每次 docker run 也需要帶入許多參數,來設定環境變數、port 映射、volume 掛載,這很不方便

這時我們就可以使用 docker compose 來輕鬆以宣告式參數化的方式啟動和管理容器

version: '3.8'
services:
react-app-dev:
build: .
ports:
- "5173:5173"
volumes:
- .:/app
- /app/node_modules
command: npm run dev -- --host --mode development
env_file:
- .env.development

這時候使用 docker-compose up react-app-dev 就可以輕鬆啟動 container,並設定必要配置

現在知道單個容器的簡單配置使用,增廣成 MERN stack 就會比較輕鬆了

技術總結

  • 去除多餘
    • 有意識地挑選學習材料,並去除材料內的雜訊,不適合自己就不要戀眷,可以果斷換一個
    • 若可以,課程元素以自己熟悉的背景知識為主,盡量以既有知識去學習新知識
  • 分割複雜
    • 先大致疏理,知識圖譜中,知識點彼此之間的元素相依性
    • 例如,知識點彼此是否傾向獨立?還是需要先懂 甲 -> 乙 -> 丙,才能懂 A、B、C?(又可能 A、B、C 三者又彼此相依)
    • 先掌握元素相依性比較單純的基礎知識,再以此作為磚頭去蓋房子
    • 如果可以,將常常被複用的知識點 尋找直覺意涵與類比譬喻
  • 交替實例
    • 從做中學,先能掌握核心概念並完成一個 MVP 最重要,因為要先夯實核心基礎,內化為長期知識,為下一步打地基

再來就是...一開始可以先以 GUI 為主,比較好入門,不用逼自己一開始就要用 command 指令,後面熟了再來帥

其實發現這個跟敏捷的精神也很類似(真的是萬物相通),這次使用這個方法拿實踐 Docker 入門,發現曲線非常平滑,甚至「分割複雜」這一塊可以先交給 GPT 幫你庖丁解牛

因為學了一下午的 Docker 就覺得靈感來了,想快點記錄下來,技術部份若有錯誤請海涵