2018年4月6日 星期五

整理 rust module 的安排方式

故事是這樣子的,兩年前因為傳說中的 jserv 大神的推薦,我讀了 Understanding Computation 這本書,讀完覺得學到很多東西,深受啟發;後來大概花了兩個月的時間,用Rust 重寫了裡面所有的範例程式碼,目前在 github 上查了一下,我應該是除了原作實作之外,實作最完整的一個,可謂一人之下,萬人之上(誤。
最近因為一些原因,把之前的實作打開來看,馬上關上,假的!趕快在筆電前面打坐。
當初到底怎麼寫這麼醜,還查到有些章節的內容沒有實作完,那時候可能太難不會寫,先跳過結果就忘了QQ……最近這一兩個禮拜陸續花了一點時間整理。

這次整理的一大修正,是把本來是散在各處的原始碼,重新照 rust 慣例統整到 src 資料夾下面,並使用 cargo 管理,帶來的好處包括有:可以一次 cargo build 編譯所有程式;引入 cargo test 代替本來編譯成執行檔用 println debug 的實作;在各章的內容間重用 module ,提升重用比例;另外也能使用網路上其他人寫的 Rust module(其實這才是原初整理的目的)。
例如在我之前實作的程式碼,在寫 finite automata 時,dfa, nfa 各自有一個實作,使用 u32 作為狀態;但到了 regular expression 的時候,為了產生 nfa 就不能用 u32 作為狀態,於是我複製了一版 nfa,改成用 object pointer 作為狀態,兩者程式碼的重複率就非常高,這次也一併改成 generic 的 nfa 實作,兩邊就能分享同一套程式碼。

本來以為整理就是把程式碼搬一搬,也差不多就結束了,結果不是(當然有一部分是我自己蠢),但也是因為這個機會,弄懂的 Rust 的 module 系統,這裡作個記錄。

在一個 rust project 當中,最重要的都放在 src 資料夾下面,包括要編成函式庫或執行檔的原始碼都在這裡,其他像編譯結果的 target、文件的 doc、測試的 test ,相對來說比較沒這麼重要(好啦還是很重要,只是不是本文要討論的重點)。

rust 的編譯是由 cargo.toml 所驅動,一個 rust 模組只能編出一個 rlib,由 cargo.toml 的 [lib] 所指定,例如我這個專案指定 library 名稱跟路徑如下:
[lib]
name = "computationbook"
path = "src/lib.rs"
如果不設定的話,cargo 會預設編譯 src/lib.rs,並自動從資料夾名稱產生 library 名稱;這個 library 名稱非常重要,先把它記著。

函式庫的實作就在 lib.rs 裡面,包含函式庫所有的函式,可以加上 pub 讓外界可以取用;當函式多的時候,就開始需要幫他們分門別類,也就是 rust 的 module:
fn libraryfun () {}
mod modulename {
 fn libraryfun () {}
}
用起來有點像 C++ 的 namespace,上面例子中,兩個 libraryfun 是不會衝突,一個是 libraryfun,一個是 modulename::libraryfun。
然後 Rust 以它預設什麼都不開放的嚴謹著稱,上面的不寫 pub mod,pub fn 的話,外面是無法取用的。

函式更多,可以把整個 module 移到另一個檔案,lib.rs 裡面只留下 mod 宣告:
mod modulename;
內容移到 modulename.rs 裡面;或者有足以獨立出來的功能,可以放到 modulename 資料夾下,並加上 modulenmae/mod.rs 來表示這個資料夾是一個 module。

module 大致的規則就是這樣
一:可直接在檔案內定義。
二:只寫 mod modulename,內容放在其他檔案。
三、只寫 mod modulename,內容放在同名且內含 mod.rs 的資料夾內。
注意第二、三個方法互相衝突的,不能有 mymodule.rs 跟 mymodule/mod.rs 同時存在,rust 會跳出編譯錯誤。
另外有一個特例是在 src 下,只有 lib.rs 有權限宣告 submodule,例如 lib.rs 宣告一個叫 network 的 module 並把內容放在 src/network.rs,network.rs 就不能再宣告它有一個 module server。
// src/lib.rs
mod network;
// src/network.rs
mod server; // compile error
這裡的 error 訊息很詭異,是 file not found for module ‘server’,但初遇時覺得見鬼,檔案就在那邊你跟我說沒有…。
正確作法是把 network.rs 移到 network 資料夾中,改名為 mod.rs 指明這個資料夾下有哪些 module,這時候 src/lib.rs 裡面的 mod network; 就會指向 network/mod.rs 裡寫的 mod;把 server.rs 放在network 資料夾中,就適用上述的規則二,在 mod.rs 裡只寫 mod server;,內容放在其他檔案。

講完 module 的定義,現在可以來講引用,這部分要分成兩種,一是 src 下函式庫的引用:
上面第三種規則的,拿我的 dfa module 為例, mod.rs 定義了 dfarulebook.rs 跟 dfa.rs ,dfa 需要 dfarulebook,因為他們都在 dfa module 內,要引用時就用:
use super::dfarulebook::{DFARulebook};
因為這些檔案不太可能會分開,用 super 的好處是無論 dfa 資料夾到哪這個參照都不會變;在 mod.rs 裡面實作測試的 module 也是一樣,測試當然需要原本 module 裡的東西,這個時候也是使用 super:
mod dfarulebook;
#[cfg(test)]
mod tests {
  use super::dfarulebook::{DFARulebook};
同樣是第三種規則的 mod.rs,mod.rs 本身就是 dfa module,它需要用到 dfarulebook.rs 的內容,則是:
mod dfarulebook;
mod dfa;
use self::dfarulebook::{DFARulebook};

來自 dfa 之外的引用就要用完整路徑,從 src 開始一路指定:
use the_simplest_computer::dfa::dfarulebook::{DFARulebook};
你可以想像,從 src/lib.rs 開始,要可以透過一連串的 mod.rs 找到我們要的 module;上面的 lib.rs 裡就有 mod the_simplest_computer; ;the_simplest_computer/mod.rs 裡有 mod dfa; 一路像串棕子一樣把各模組串起來,如果有地方沒串好,cargo 就會回報錯誤。

再來是執行檔,這裡我也是試好久才試出來。
現在的 cargo 可以在 Cargo.toml 裡面,用 [[bin]] 欄位指定多個執行檔目標,不然就是預設的 src/main.rs,這跟 library 不同,一個 Cargo.toml 就只能有一個 [lib] 目標。
這裡關鍵的點就是:執行檔不是 library 的一部分,cargo 先從 lib.rs 編譯出函式庫,然後編譯執行檔跟函式庫做連結;執行檔要用編譯的函式庫,就要先宣告 extern crate,然後每層 use 都在前面多加上函式庫,上面說要記著的函式庫名稱就是這裡要用到:
extern crate computationbook;
use computationbook::the_simplest_computer::dfa::dfarulebook::{DFARulebook};
超級長對吧XD
如果函式庫名稱是 cargo 自動產生,可以去 target/debug/ 看它編譯出什麼,我上面的例子就是:libcomputationbook.rlib

故事大概就到這裡,這次總算弄懂 cargo 如何管理各 module 了,感謝大家收看。

在這裡就雷一下大家,下一篇就要來說一下,本來整理這個 project 要做的東西,估計會是一篇理論跟實作兼具的文章,敬請期待。

本文之完成,感謝以下參考資料:
Cargo guide: https://doc.rust-lang.org/cargo/guide/
Rust module guide: https://doc.rust-lang.org/book/second-edition/ch07-01-mod-and-the-filesystem.html
minisat-rust 專案,因為它編得過我編不過,它的複雜度又剛剛好夠複雜,就拿來研究了一番:https://github.com/mishun/minisat-rust

2018年3月18日 星期日

實用的gdb 指令

最近工作上大量使用到 gdb,想說來整理一下一些常用的 gdb 使用方式,以及對應的場景;當然,這絕對不是 gdb 完整的功能介紹,只是目前我遇到比較多的使用方式而已。

檢視原始碼:
gdb 的文字介面可以顯示四種視窗:命令,原始碼,組合語言跟暫存器,與視窗相關的鍵盤組合:
Ctrl + x + a 它會分成上原始碼下命令兩個視窗
Ctrl + x + o 切換焦點所在的視窗,如果焦點視窗放在原始碼那邊,命令視窗的一些鍵會無效,例如上下鍵會變成原始碼視窗的上下瀏覽原始碼,得用 Ctrl + P (previous) 跟 Ctrl + N (next) 才能瀏覽歷史命令。
使用Ctrl + x + 1,Ctrl + x + 2 可以設定原始碼視窗分為一個或兩個,連續 Ctrl + x + 2 會依序在原始碼+組合語言、組合語言+暫存器、暫存器+組合語言三種組合中切換。
這功能可以直接看到現在執行到哪裡,特別是進到除錯熱區時,gdb 用 list 指令都會自動加10 行,要印出當下除錯的程式總有點難度,用這個可以徹底排除這個問題;不過我也覺得還沒到除錯熱區的時候,配合編輯器來看原始碼即可。

快捷鍵(這個不止是 gdb,而是大部分 shell 都適用):
除了上述的 Ctrl + p, Ctrl + n,Ctrl + l 可以清空目前的 buffer,Ctrl +w, Ctrl + u 可以刪除命令列上一個單字或全部內容,也滿好用的。特別是 gdb 裡面 shell 下清空 buffer 的指令 clear 沒有辦法用,Ctrl + l 使用機會滿大的(前提是原始碼視窗沒開)。

中斷點設置:
有關gdb 裡面的四個點:breakpoint, watchpoint, catchpoint跟tracepoint,到現在我也只用過前兩個,99 %都是 breakpoint:
breakpoint 又分為永久跟暫時,對應 break/tbreak 後面接要中斷的位置,可以用函數名稱或者 <sourcefile>:<linenum> 兩種格式。
還可以在 breakpoint 的後面加上條件,例如 break <function> if <var> == 10,就能開始 debug 某個狀況下的執行,這在除錯有迴圈或多次呼叫函式的程式時非常好用。
start 指令,自動設置 temporary breakpoint 在主程式開始處,C/C++ 就是 main。
Watchpoint 則是用watch/rwatch 指令設置,可以監看一個變數什麼時候被動過,指令監看的目標可以是變數、記憶體位置(如 watch *0x12345678)或是幾個變數的運算也可以。
watch、rwatch、awatch 分別監看變數寫入、讀取跟讀寫。

無論是breakpoint watchpoint其實都是… breakpoint(watchpoint 有時叫 data breakpoint),可以用 info break 看到所有相關設定,還有例如它已經碰到幾遍之類。
有一個相關的技巧如下,可以計算某個函數究竟遇到幾次:
break foo
ignore <breakpoint num> 100000 //大到遠超過可能執行的數量
continue
等程式執行完就能用 info break 看到這個 breakpoint 被碰幾次。

另外 debug 時,可以利用save breakpoints <filename>把設好的breakpoint 存到檔案裡,下次可以直接source它們,save 的 breakpoint 最好只設在函數名稱上面,行數可能在debug 時變化,下次source 時就會break在不同的地方了。
我習慣上會存成 gdbinit 這樣的檔案,一進 gdb 時 source gdbinit 即可。

修改執行:
執行中可以用
set var <variable name>=<value>
set <memory location>=<value
的方式修改變數/記憶體位址內容。
例如我們發現某個條件被跳掉會導致錯誤結果,可以在 gdb 內用 set 修改變數,使它符合條件,省去重新編譯的麻煩(甚至一些狀況下,修改程式可能動到本來的邏輯,或很難使它符合條件)

跳出函數執行:
指令 finish,執行直到離開當下函數(當然遇到 breakpoint 還是會被擋)。

介面
上面的功能,有些因為最近比較常用 ddd 而非 gdb,也就沒有常用到。
另外強者我同學強強林有推一款 web-based 的 gdb gui:https://github.com/niklasb/webgdb
,據說 vscode 的 debug 功能也滿生猛的,也許找時間都來試試看。

2018年3月11日 星期日

<令人難以理解的軟體工程師生涯>心得

先前在 Facebook 上面看到這篇文章(因為怕有人沒 Facebook 權限,所以貼關鍵評論網的轉載),看了看有些心得,就打在這裡:
https://www.thenewslens.com/article/91122
話說回來,我看了關鍵評論網的引言<我們想讓你知道的是>,還是不知道它想讓我知道什麼……。
其實這篇文有點跟原文對幹的意思,身為資淺軟體工程師這麼做好像有點不識大體,不過…反正我這個小咖 blog 也沒人看,大概也沒差,而且大部分的內容,多半去看<人月神話>就有了XDD。

十倍軟體生產力是否存在?我的答案是:Yes,肯定的 Yes
這件事情不單純在軟體,各行各業都是如此,熟練的高手能得到比一般平庸的操作員數倍的生產力,軟體奇特之處在於,生產力可以高到十倍甚至更高之譜,遠超過其他行業頂尖高手和一般人之間的差距。
箇中原因,在人月神話裡面已經有解釋:軟體是人類有史以來發明過最複雜的東西,是邏輯和數學的直接表達,寫好軟體就是一次次表達腦中的虛擬結構,而頂尖的腦袋的虛擬能力,遠在一般腦袋之上;之前就有個<學長告誡>:不要去問數學高手(aka 拿了兩次數奧金牌)數學問題,他講的話你聽不懂--他們虛疑化的層次遠超過你,你要想三步的東西他一步就講完了。
學會寫程式之後(差不多就大學吧),認識的很多很強的高手,真的是活生生體會到所謂的十倍-百倍生產力這件事,這些人現在要不是在 Google 大殺四方就是創業去了。
自己有時多少也會懷疑自己到底能不能做到那個程度,而且我相信,諸位軟體工程師的生涯中,一定也遇到過非常多10倍生產力的高手,也因此原本這篇貼文,才會獲得了如此大的迴響。

於是我們回到文中的大哉問:如果已經有了頂尖、十倍生產力的軟體工程師,那為何需要平庸工程師(就像我這種)?
軟體世界不是人多好辦事,但人少不成事。
原作者用了三個月的時間,改寫數萬行的程式為數千行漂亮的程式碼,非常厲害。
但一個 LInux 核心到現在有兩千萬行程式碼,如果像他這樣三個月估且算一萬行好了,要花 6000 個月,也就是 500 年才能寫完,這是可以接受的嗎?chrome 瀏覽器 500 萬行,firefox 一千萬行,每個都要十倍工程師孜孜不倦花好幾年手工打造嗎?還不要提現下消費者是每半年到一年要看到一次改版噢。
https://www.quora.com/What-is-the-biggest-program-lines-of-code-ever-made
這還不算上自己腦袋打結的時間,更不用提即便是天才也會有他的盲點,學的知識的它的上限,懂CPU的不一定懂網路,會演算法的可能不善於實作,懂資訊安全的可能不會硬碟……,系統長大之後會出現許多測試整合的工作,單打獨鬥鐵定有一個上限存在。
大型軟體本來就要綜合眾人之力,這又回到本來的問題:集合眾人就要溝通問題本質的數學邏輯,然後發生誤解跟整合問題,就算團隊全部都是10倍生產力工程師也無可倖免,以前總以為一流的軟體公司,像什麼 Google, Amazon 什麼的,裡面的程式一定都是藝術品等級,後來問問任職的同學,才發現根本不是這回事,有點年紀的大公司,軟體總是變成一團屎,還聽說過有幾個檔案變成 magic code ,根本沒人敢動的。
在跟強者我同學一哥聊天時,就有說到:「公司花錢請你來是要解決問題,不是把公司的東西變藝術品」、「每家公司都有鳥事,所以沒方向的話就選薪水高的」聽起來很世儈,但現實如此。

其實這幾年來,一直覺得台灣軟體最大的問題,並不是我們沒有夠多的10倍生產力軟體工程師,10倍軟體工程師本身就是一個異數,也許培養一群人裡,比例也就這麼多;更何況,過度看重十倍軟體工程師,顯然把世界想得太簡單,以為不管什麼問題,只要派出一位10倍軟體工程師都能迎刃而解,但很多問題卻不是如此。
我覺得我們最缺的,是培育出許多平庸的軟體工程師之後,如何拉一位 10倍軟體工程師,把大家組織成部隊來打群架(順帶一提,所謂的平庸工程師, 是指:給定一個嚴謹的規格,可以四平八穩完成開發,不要寫出會 buffer overflow 之類的低級錯誤,足矣。),我們很喜歡強調個人能力不足,但現在這個時代,就是要靠打群架才能打勝仗,如何把 10 位平庸的工程師組織起來,搭配一位 10 倍生產力的工程師,變成 50 倍生產力的團隊?只靠單打獨鬥,我們要如何挑戰百萬行甚至千萬行的系統?
也許哪天我們會看到:原本千萬行的程式,大家一起重寫的只剩下百萬行,功能不變,效能更好,而且架構儼然。前幾天完成 Alpha 版之後,大家不禁開瓶慶祝,笑聲充斥整個辦公室。

這大概才是台灣要追求的方向。

話又說回來,其實寫程式就只是寫程式,就算不寫程式跑去教書,腦袋還是可以想程式邏輯;上班寫無聊的程式,下班還是可以繼續學有趣的東西;就算當管理職不寫程式,把軟體架構切好,交給下面的團隊幫忙開發,對專案就沒有任何助益?誰說只有寫程式,還要寫得快又寫得漂亮才能貢獻社會?
理論上我們可以用寫程式數倍的時間,一行一行驗證我們的程式碼有沒有問題,把程式改到完全不會出錯,但世界不鳥這些,<不吃飯不睡覺,打起精神數鈔票>比較重要,程式當掉資料飛了,跟客戶土下座就好,反正沒有一次土下座解決不了的問題,如果有,就兩次。
寫程式其實沒有這麼偉大。

我們持續寫下去,只是因為寫程式很快樂,解決問題很快樂,重新打造輪子很快樂(等等…),覺得自己又變厲害了很快樂,完成大系統裡的小螺絲,很快樂
每位程式設計師,其實都是無可救藥的樂觀主義者。

2018年3月4日 星期日

Coursera Introduction to Logic

前些日子開始修了 coursera 上 stanford 大學開的 Introduction to Logic,修完而且有學到東西,其實個人習慣上滿常亂加一些 coursera 上的課程,有些聽一聽覺得無聊就沒聽完,沒聽完的就不會在這裡推薦就是。

下面是基本資訊:
課程名稱:Introduction to Logic
開課學校:Stanford University
授課教授:Michael Genesereth
開課時間:10 周
教學方式:靜態講義
通過方式:每週完成指定的作業,下面會詳述作業內容。

課程內容涵蓋基礎邏輯的概念,符號介紹,邏輯證明跟歸納證明,沒有到太複雜的內容,如果在台灣的大學有修邏輯的話,應該也是差不多的內容,例如通識很熱門的<邏輯丙>,應該內容也差不多。
不像其他課程是教授親自上陣講課,這門課上課的方式是看靜態文字講義(似乎有簡體中文不過我是看英文的),講義看過之後會自動記錄看過,相對來說看靜態文字比看教授講解還要無聊一些,而且有點吃英文能力。
課程內容很大一塊是放在 fitch system,並且證明也都是用 fitch system 來證明,每周作業會出約莫 5 到 10 題的題目,有些是選擇題,選到正確的選項即可;有些則是證明題,要證出所要的結果;有幾週會加一些邏輯遊戲跟參考影片,例如邏輯踩地雷,或者看教授演示一個邏輯表達系統。
證明題使用的是一個 javascript 寫的證明系統,可以用滑鼠選擇敘述,還有要使用的運算來完成證明,一開始有一點難上手,不過熟練之後就會覺得設計真的非常厲害,For all 跟 Exist 的符號代換都會自動完成,點一點就發證明完成了。唯一卡比較大的是在第八週的歸納法,久違地動用紙筆在紙上釐清議義範例證明的思路,想通之後在系統上重現就清楚多了。

學完之後真心覺得有學到東西,我從來沒想過原來連 (p => q) => (~q => ~p), (p => q) => (~p | q) , (p | ~p) 這樣基礎的邏輯也可以證明,但是千真萬確,證過一次才知道這真的可以用邏輯推出來,所以以前說的那些:前題錯就可以推出任何東西,「如果月亮是起司做的,那麼月亮可以吃」都是真的。
其他要注意的事情:寫作業的系統不知道為什麼,用 chrome 開的話會有些問題,用 firefox 來操作就沒問題。
另外,我誠心建議大家睡前不要讀邏輯,讀邏輯當然要沐浴更衣正襟危坐有一次念完之後睡覺,結果在夢裡想歸納證明………,醒來累得半死。

2018年1月25日 星期四

使用 git submodule 管理 project 所需的其他模組

故事是這樣子的,最近在寫一些分析資料的程式,用的是 python 跟 numpy。
一開始改寫的時候,發現一開始 load 資料的地方,Python 實在太慢了,載入 20000 筆資料耗時超過 150 秒,後來決定用 C++ 改寫載入數據的部分,同時利用別人寫好的 cnpy 這個 project,寫出 numpy 檔案作分析,結果載入速度竟然一口氣降到 4.5s,加速 15 dB,太可怕了(yay。
為了要使用 cnpy 這個 project,順手研究了一下要如何給使用 git submodule,這篇文章就做個介紹:

基本上submodule 利用的場合,就如我上面說,我的 project 要用到其他 project 的程式碼,我希望讓我的使用者能直接拿到其他 project 的程式碼,這樣他們不用自己再去載,麻煩之外可能還會載到錯誤的版本。
另一方面,我們又不想把對方的程式全部加到我的 repository 裡面,這樣會帶來不好的後果,上游的程式碼修改,要自己手動更新,沒辦法用 git 的方式更新,增加錯誤的機會。
git 針對這個使用情景的解決方式就是 submodule:

遇到 submodule 的時候通常有兩種狀況,第一種比較常見的,是載了一個別人的project,發現裡面有用到 submodule,例如知名的補齊工具 YouCompleteMe ,裡面針對各種語言的剖析工具都是 submodule,這些專案載下來的時候, submodule 裡面都還是空的,要先用下列指令把 submodule 載下來:
git submodule init
git submodule update
或者可以用一行指令解決:
git submodule update --init --recursive
--recursive 會在 submodule 裡面還有 submodule 的時候,一口氣都設定好。
又或者可以在 clone 專案的時候就指定要一齊複製 submodule(不過通常在 clone project 的時候還不知道裡面有 submodule,所以…通常不會這樣下):
git clone --recurse-submodules url

第二種狀況如我上面所述,我們自己新增一個 submodule,我要做的就是新增 cnpy 為我的 submodule:
git submodule add git@github.com:rogersce/cnpy.git cnpy
後面的 cnpy 是指定 submodule 要放在哪個資料夾裡面,通常名稱都跟 project 本來的名稱相同,才不會搞混;這時候記得會開始把這個 project 下載下來,接著檢視 status 的話會看到下面的內容:
new file: .gitmodules
new file: cnpy
.gitmodules 檔案裡面記錄了 submodule 的名字,現在的路徑以及遠端 url,這是可以用 add 及 commit 將這個 submodule 保存下來。

在一個有 submodule 的專案裡面工作,會和一般的工作內容稍有不同,當進到 submodule 內部的時候;submodule 從外面來看只是一個參照,如果真的進到這個資料夾,用起來就會像另外一個 git repository 一樣,一樣會有 master 等等 branch,可以用 remote 拉別人的修改下來,或者自己送 commit 出去。
比較讓人疑惑的通常是在外面的 project,當內部的內容有修改的時候,外面會出現一些讓人很疑惑的訊息,例如當我們對 cnpy 這個 project 新增一個 commit,從外面會看到這樣的訊息:
git status
modified: cnpy (new commits)
這則訊息的意思是,cnpy 這個 submodule 有了修改,修改的內容是新增了 commits;可以把git submodule 想成一個快照,現在 submodule 的狀態已經脫離這個快照,從 git diff 就會看出差別,最下面是 commit 的修改訊息:
git diff
diff --git a/cnpy b/cnpy
index f19917f..8f997be 160000
--- a/cnpy
+++ b/cnpy
@@ -1 +1 @@
-Subproject commit f19917f6c442885dcf171de485ba8b17bd178da6
+Subproject commit 8f997be1f87279f09054acbdb896162b1e9d3963

這時對這個 submodule 做 add, commit,就會更新這個 submodule 的快照值;另外如果我們想要 submodule 維持在之前的快照上,用 git submodule update ,git 即會將 submodule 簽回到當初記錄的版本:
git submodule update
Submodule path 'cnpy': checked out 'f19917f6c442885dcf171de485ba8b17bd178da6'

不過 update 之後會有一些不好的效果,因為這時 submodule 被強制簽出 f19917 這個 commit ,裡面就出現了一些沒有 commit 的修改,在這裡有內容未被 commit,所以它會顯示:
git status modified: cnpy (untracked content)
從外面會看到 submodule 有修改,但要消掉這個 untracked content 的訊息,就要進到 submodule 資料夾裡面,用 checkout 或 clean 的方式,讓 submodule 的狀態回到 clean 才行。
但同時也要注意的,這時候 submodule 就處在 detach HEAD 的狀態(在上面的例子,submodule 落後 master 一個 commit ),這時進到 submodule 做些 commit,這些 commit 並沒有 branch 參照,下一次再下 submodule update 的時候,所做的修改就會消失,如果有要修改的話,建議要先在 submodule 裡面生成一個 branch 來保留修改。

另外一個比較需要注意的,大概就是在移動 submodule 的參照的時候,儘量可以用 git mv 的方式來移動,用 os 本身的 mv 似乎比較容易出問題。

我想 submodule 我們就講到這裡,下面這篇官方的參考資料:
https://git-scm.com/book/en/v2/Git-Tools-Submodules
裡面還有很多 git submodule 神奇的用法,例如從外面用 git submodule 指令一口氣更新所有 submodule 的狀態到最新,把所有 submodule 現下的狀態推送到遠端,等等。
但我個人認為 submodule 相對來說是比較偏門的指令,自己也是用了這麼久,最近才第一次用到 submodule,大家還是用到再來查文件比較實在;另外話又說回來, git submodule 能管理的相關 project 數量也是有個限度,數量多到一定程度,submodule 也會顯得捉襟見肘,因而 google android 才會另外推 repo 這樣大量 git repository 的管理工具吧。