Ⅰ Django源碼閱讀 (一) 項目的生成與啟動
誠實的說,直到目前為止,我並不欣賞django。在我的認知它並不是多麼精巧的設計。只是由功能堆積起來的"成熟方案"。但每一樣東西的崛起都是時代的選擇。無論你多麼不喜歡,但它被需要。希望有一天,python能有更多更豐富的成熟方案,且不再被詬病性能和可維護性。(屁話結束)
取其精華去其糟粕,django的優點是方便,我們這次源碼閱讀的目的是探究其方便的本質。計劃上本次源碼閱讀不會精細到每一處,而是大體以功能為單位進行解讀。
django-admin startproject HelloWorld 即可生成django項目,命令行是exe格式的。
manage.py 把參數交給命令行解析。
execute_from_command_line() 通過命令行參數,創建一個管理類。然後運行他的 execute() 。
如果設置了reload,將會在啟動前先 check_errors 。
check_errors() 是個閉包,所以上文結尾是 (django.setup)() 。
直接看最後一句 settings.INSTALLED_APPS 。從settings中抓取app
注意,這個settings還不是我們項目中的settings.py。而是一個對象,位於 djangoconf\__init__.py
這是個Settings類的懶載入封裝類,直到 __getattr__ 取值時才開始初始化。然後從Settings類的實例中取值。且會講該值賦值到自己的 __dict__ 上(下次會直接在自己身上找到,因為 __getattr__ 優先順序較低)
為了方便debug,我們直接寫個run.py。不用命令行的方式。
項目下建個run.py,模擬runserver命令
debug抓一下setting_mole
回到 setup() 中的最後一句 apps.populate(settings.INSTALLED_APPS)
開始看 apps.populate()
首先看這段
這些App最後都會封裝成為AppConfig。且會裝載到 self.app_configs 字典中
隨後,分別調用每個appConfig的 import_models() 和 ready() 方法。
App的裝載部分大體如此
為了方便debug我們改寫下最後一句
res的類型是 Command <django.contrib.staticfiles.management.commands.runserver.Command object at 0x00000101ED5163A0>
重點是第二句,讓我們跳到 run_from_argv() 方法,這里對參數進行了若干處理。
用pycharm點這里的handle會進入基類的方法,無法得到正確的走向。實際上子類Commond重寫了這個方法。
這里分為兩種情況,如果是reload重載時,會直接執行 inner_run() ,而項目啟動需要先執行其他邏輯。
django 項目啟動時,實際上會啟動兩次,如果我們在項目入口(manage.py)中設置個print,會發現它會列印兩次。
第一次啟動時, DJANGO_AUTORELOAD_ENV 為None,無法進入啟動邏輯。會進入 restart_with_reloader() 。
在這里會將 DJANGO_AUTORELOAD_ENV 置為True,隨後重啟。
第二次時,可以進入啟動邏輯了。
這里創建了一個django主線程,將 inner_run() 傳入。
隨後本線程通過 reloader.run(django_main_thread) ,創建一個輪詢守護進程。
我們接下來看django的主線程 inner_run() 。
當我們看到wsgi時,django負責的啟動邏輯,就此結束了。接下來的工作交由wsgi伺服器了
這相當於我們之前在fastapi中說到的,將fastapi的app交由asgi伺服器。(asgi也是django提出來的,兩者本質同源)
那麼這個wsgi是從哪來的?讓我們來稍微回溯下
這個settings是一個對象,在之前的操作中已經從 settings.py 配置文件中獲得了自身的屬性。所以我們只需要去 settings.py 配置文件中尋找。
我們來尋找這個 get_wsgi_application() 。
它會再次調用 setup() ,重要的是,返回一個 WSGIHandler 類的實例。
這就是wsgiapp本身。
load_middleware() 為構建中間件堆棧,這也是wsgiapp獲取setting信息的唯一途徑。導入settings.py,生成中間件堆棧。
如果看過我之前那篇fastapi源碼的,應該對中間件堆棧不陌生。
app入口→中間件堆棧→路由→路由節點→endpoint
所以,wsgiapp就此構建完畢,伺服器傳入請求至app入口,即可經過中間件到達路由進行分發。
Ⅱ FastAPI 源碼閱讀 (一) ASGI應用
本章開啟 FastAPI 的源碼閱讀,FastAPI是當下python web中一顆新星,是一個劃時代的框架。從誕生便是以快速和簡潔為核心理念。
它繼承於 Starlette ,是在其基礎上的完善與擴展。詳細內容可以翻看我之前的源碼閱讀。
openapi() 與 setup() 是在初始化階段,對 OpenAPI 文檔進行初始化的函數。
而 add_api_route() 一直到 trace() ,是關於路由的函數,它們都是直接對 router 的方法傳參引用。所以這些放在解讀 routing.py 時一並進行。
除了 Starlette 原生的參數,大量參數都是和API文檔相關。
而路由從 Starlette 的 Router 換成了新式的 APIRouter
這兩個概念查詢了大量文檔才搞明白。他們主要是關於文檔與反向代理的參數。當使用了Nginx時等反向代理時,從Uvicorn直接訪問,和從Nginx代理訪問,路徑可能出現不一致。比如Nginx中的Fastapi根目錄是 127.0.0.1/api/ ,而Uvicorn角度看是 127.0.0.1:8000/ 。對於API介面來說,其實這個是沒有影響的,因為伺服器會自動幫我們解決這個問題。但對於API文檔來說,就會出現問題。
因為當我們打開/docs時,網頁會尋找 openapi.json 。他的是寫在html內部的,而不是變化的。這會導致什麼問題?
例如當我們從Uvicorn訪問 127.0.0.1:8000/docs 時,他會尋找 /openapi.json 即去訪問 127.0.0.1:8000/openapi.json (了解前端的應該知道)
但是假如我們這時,從Nginx外來訪問文檔,假設我們這樣設置Nginx:
我們需要訪問 127.0.0.1/api/docs ,才能從代理外部訪問。而打開docs時,我們會尋找 openapi.json 。
所以這時,它應該在 127.0.0.1/api/openapi 這個位置存在。
但我們的瀏覽器不知道這些,他會按照 /openapi.json ,會去尋 127.0.0.1/openapi.json 這個位置。所以他不可能找到 openapi.json ,自然會啟動失敗。
這其實是openapi文檔前端的問題。
root_path ,是用來解決這個問題的。既然 /openapi.json 找不到,那我自己改成 /api/openapi.json 不就成了么。
root_path 即是這個 /api ,這個是在定義時手動設置的參數。為了告訴FastAPI,它處在整個主機中的哪個位置。即告知 所在根目錄 。這樣,FastAPI就有了"自知之明",乖乖把這個 前綴 加上。來找到正確的 openapi.json
加上了 root_path , openapi.json 的位置變成了 /api/openapi.json 。當你想重新用Uvicorn提供的地址從代理內訪問時,他會去尋找哪?沒錯 127.0.0.1:8000/api/openapi.json ,但我們從代理內部訪問,並不需要這個 前綴 ,但它還是 「善意」 的幫我們加上了,所以這時候內部的訪問失靈了。
雖然我們不大可能需要從兩個位置訪問這個完全一樣的api文檔。但這點一定要注意。
我在翻官方文檔時,看到他們把 root_path 吹得天花亂墜,甚至棄用了 openapi_prefix 參數。但最後是把我弄得暈頭轉向。
這樣要提到 servers 這個參數,官方首先給了這么段示例,稍作修改。
當我們打開API文檔時
我們可以切換這個 Servers 時,底下測試介面的發送鏈接也會變成相應的。
但是記住,切換這個server,下面的介面不會發送變化,只是發送的host會改變。
這代表,雖然可以通過文檔,測試多個其他主機的介面。但是這些主機和自己之間,需要擁有一致的介面。這種情況通常像在 線上/開發伺服器 或者 伺服器集群 中可能出現。雖然不要求完全一致,但為了這樣做有實際意義,最好大體上是一致的。
但是我們看到,這是在代理外打開的,如果我們想從代理內打開,需要去掉 root_path 。會發生什麼?
我們將 root_path 注釋掉:
如果想解決這個問題,只需要將自身手動加入到 Servers 中。
關於 root_path_in_servers ,當 root_path 和 servers 都存在時, root_path 會自動將自己加入到 servers 中。但如果這個置為False,就不會自動加入。(默認為True)
API文檔實際上以字元串方式,在FastAPI內部拼接的。實際上就是傳統的 模板(Templates) ,這個相信大家都很熟悉了。優點是生成時靈活,但缺點是不容易二次開發。fastapi提供了好幾種文檔插件,也可以自己添加需要的。
這么長一大串,實際上就一句話 self.router.add_api_route() ,其他剩下的那些我暫且省略的,其實基本都是這樣的。就是調用router的一個功能。下面我用省略方式將它們列出。
可以看到有些在這里就做了閉包,實際上除了這里的'add_api_route()'他們最終都是要做閉包的。只是過程放在里router里。它們最終的指向都是 router.add_api_route() ,這是一個添加真正將 endpoint 加入到路由中的方法。
FastAPI 添加路由的方式,在starlette的傳統路由列表方式上做了改進,變成了裝飾器式。
其實就是通過這些方法作為裝飾器,將自身作為 endpoint 傳入生成 route 節點,加入到 routes 中。
FastAPI的入口沒有太大的變化,借用starlette的 await self.middleware_stack(scope, receive, send) 直接進入中間件堆棧。
Ⅲ 如何閱讀源代碼
」, 除了閱讀代碼以外, 沒有更好的方法. 7.在尋找bug時, 請從問題的表現形式到問題的根源來分析代碼. 不要沿著不相關的路徑(誤入歧途). 8.我們要充分利用調試器|編譯器給出的警告或輸出的符號代碼|系統調用跟蹤器|資料庫結構化查詢語言的日誌機制|包轉儲工具和Windows的消息偵查程序, 定出的bug的位置. 9.對於那些大型且組織良好的系統, 您只需要最低限度地了解它的全部功能, 就能夠對它做出修改. 10.當向系統中增加新功能時, 首先的任務就是找到實現類似特性的代碼, 將它作為待實現功能的模板. 11.從特性的功能描述到代碼的實現, 可以按照字元串消息, 或使用關鍵詞來搜索代碼. 12.在移植代碼或修改介面時, 您可以通過編譯器直接定位出問題涉及的范圍, 從而減少代碼閱讀的工作量. 13.進行重構時, 您從一個能夠正常工作的系統開始做起, 希望確保結束時系統能夠正常工作. 一套恰當的測試用例(test case)可以幫助您滿足此項約束. 14.閱讀代碼尋找重構機會時, 先從系統的構架開始, 然後逐步細化, 能夠獲得最大的效益. 15.代碼的可重用性是一個很誘人, 但難以理解與分離, 可以試著尋找粒度更大一些的包, 甚至其他代碼. 16.在復查軟體系統時, 要注意, 系統是由很多部分組成的, 不僅僅只是執行語句. 還要注意分析以下內容: 文件和目錄結構|生成和配置過程|用戶界面和系統的文檔. 18.可以將軟體復查作為一個學習|講授|援之以手和接受幫助的機會. ++++++++++++++++++++ 第二章: 基本編程元素 ++++++++++++++++++++ 19.第一次分析一個程序時, main是一個好的起始點. 20.層疊if-else if-…-else序列可以看作是由互斥選擇項組成的選擇結構. 21.有時, 要想了解程序在某一方面的功能, 運行它可能比閱讀源代碼更為恰當. 22.在分析重要的程序時, 最好首先識別出重要的組成部分. 23.了解局部的命名約定, 利用它們來猜測變數和函數的功能用途. 24.當基於猜測修改代碼時, 您應該設計能夠驗證最初假設的過程. 這個過程可能包括用編譯器進行檢查|引入斷言|或者執行適當的測試用例. 25.理解了代碼的某一部分, 可能幫助你理解餘下的代碼. 26.解決困難的代碼要從容易的部分入手. 27.要養成遇到庫元素就去閱讀相關文檔的習慣; 這將會增強您閱讀和編寫代碼的能力. 28.代碼閱讀有許多可選擇的策略: 自底向上和自頂向下的分析|應用試探法和檢查注釋和外部文檔, 應該依據問題的需要嘗試所有這些方法. 29.for (i=0; i 30.涉及兩項不等測試(其中一項包括相等條件)的比較表達式可以看作是區間成員測試. 31.我們經常可以將表達式應用在樣本數據上, 藉以了解它的含義. 32.使用De Morgan法則簡化復雜的邏輯表達式. 33.在閱讀邏輯乘表達式時, 問題可以認為正在分析的表達式以左的表達式均為true; 在閱讀邏輯和表達式時, 類似地, 可以認為正在分析的表達式以左的表達式均為false. 34.重新組織您控制的代碼, 使之更為易讀. 35.將使用條件運行符? :的表達式理解為if代碼. 36.不需要為了效率, 犧牲代碼的易讀性. 37.高效的演算法和特殊的優化確實有可能使得代碼更為復雜, 從而更難理解, 但這並不意味著使代碼更為緊湊和不易讀會提高它的效率. 38.創造性的代碼布局可以用來提高代碼的易讀性. 39.我們可以使用空格|臨時變數和括弧提高表達式的易讀性. 40.在閱讀您所控制的代碼時, 要養成添加註釋的習慣. 41.我們可以用好的縮進以及對變數名稱的明智選擇, 提高編寫欠佳的程序的易讀性. 42.用diff程序分析程序的修訂歷史時, 如果這段歷史跨越了整體重新縮排, 常常可以通過指定-w選項, 讓diff忽略空白差異, 避免由於更改了縮進層次而引入的噪音. 43.do循環的循環體至少執行一次. 44.執行算術運算時, 當b=2n-1時, 可以將a&b理解為a%(b+1). 45.將a<<n理解為a*k, k=2n. 46.將a>>n理解為a/k, k=2n. 47.每次只分析一個控制結構, 將它的內容看作是一個黑盒. 48.將每個控制結構的控製表達式看作是它所包含代碼的斷言. 49.return, goto, break和continue語句, 還有異常, 都會影響結構化的執行流程. 由於這些語句一般都會終止或重新開始正在進行的循環,因此要單獨推理它們的行為. 50.用復雜循環的變式和不變式, 對循環進行推理. 51.使用保持含義不變的變換重新安排代碼, 簡化代碼的推理工作. +++++++++++++++++++ 第三章: 高級C數據類型 +++++++++++++++++++ 52.了解特定語言構造所服務的功能之後, 就能夠更好地理解使用它們的代碼. 53.識別並歸類使用指針的理由. 54.在C程序中, 指針一般用來構造鏈式數據結構|動態分配的數據結構|實現引用調用|訪問和迭代數據元素|傳遞數組參數|引用函數|作為其他 值的別名|代表字元串|以及直接訪問系統內存. 55.以引用傳遞的參數可以用來返回函數的結果, 或者避免參數復制帶來的開銷. 56.指向數組元素地址的指針, 可以訪問位於特定索引位置的元素. 57.指向數組元素的指針和相應的數組索引, 作用在二者上的運算具有相同的語義. 58.使用全局或static局部變數的函數大多數情況都不可重入(reentrant). 59.字元指針不同於字元數組. 60.識別和歸類應用結構或共用體的每種理由. 61.C語言中的結構將多個數據元素集合在一起, 使得它們可以作為一個整體來使用, 用來從函數中返回多個數據元素|構造鏈式數據結構|映射數據在硬體設備|網路鏈接和存儲介質上的組織方式|實現抽象數據類型|以及以面向對象的方式編程. 62.共用體在C程序中主要用於優化存儲空間的利用|實現多態|以及訪問數據不同的內部表達方式. 63.一個指針, 在初始化為指向N個元素的存儲空間之後, 就可以作為N個元素的數組來使用. 64.動態分配的內在塊可以電焊工地釋放, 或在程序結束時釋放, 或由垃圾回收器來完成回收; 在棧上分配的內存塊當分配它的函數退出後釋放. 65.C程序使用typedef聲明促進抽象, 並增強代碼的易讀性, 從而防範可移植性問題, 並模擬C++和java的類聲明行為. 66.可以將typedef聲明理解成變數定義: 變數的名稱就是類型的名稱; 變數的類型就是與該名稱對應的類型. +++++++++++++++ 第四章: C數據結構 +++++++++++++++ 67.根據底層的抽象數據類型理解顯式的數據結構操作. 68.C語言中, 一般使用內建的數組類型實現向量, 不再對底層實現進行抽象. 69.N個元素的數組可以被序列for (i=0; i 70.表達式sizeof(x)總會得到用memset或memcpy處理數組x(不是指針)所需的正確位元組數. 71.區間一般用區間內的第一個元素和區間後的第一個元素來表示. 72.不對稱區間中元素的數目等於高位邊界與低位邊界的差. 73.當不對稱區間的高位邊界等於低位邊界時, 區間為空. 74.不對稱區間中的低位邊界代表區間的第一個元素; 高位邊界代表區間外的第一個元素. 75.結構的數組常常表示由記錄和欄位組成的表. 76.指向結構的指針常常表示訪問底層記錄和欄位的游標. 77.動態分配的矩陣一般存儲為指向數組列的指針或指向元素指針的指針; 這兩種類型都可以按照二維數組進行訪問. 78.以數組形式存儲的動態分配矩陣, 用自定義訪問函數定位它們的元素. 79.抽象數據類型為底層實現元素的使用(或誤用)方式提供一種信心的量度. 80.數組用從0開始的順序整數為鍵, 組織查找表. 81.數組經常用來對控制結構進行高效編碼, 簡化程序的邏輯. 82.通過在數組中每個位置存儲一個數據元素和一個函數指針(指向處理數據元素的函數), 可以將代碼與數據關聯起來. 83.數組可以通過存儲供程序內的抽象機(abstract machine)或虛擬機(virtual machine)使用的數據或代碼, 控製程序的運作. 84.可以將表達式sizeof(x) / sizeof(x[0])理解為數組x中元素的個數. 85.如果結構中含有指向結構自身|名為next的元素, 一般說來, 該結構定義的是單向鏈表的結點. 86.指向鏈表結點的持久性(如全局|靜態或在堆上分配)指針常常表示鏈表的頭部. 87.包含指向自身的next和prev指針的結構可能是雙向鏈表的結點. 88.理解復雜數據結構的指針操作可以將數據元素畫為方框|指針畫為箭頭. 89.遞歸數據結構經常用遞歸演算法來處理. 90.重要的數據結構操作演算法一般用函數參數或模板參數來參數化. 91.圖的結點常常順序地存儲在數組中, 鏈接到鏈表中, 或通過圖的邊鏈接起來. 92.圖中的邊一般不是隱式地通過指針, 就是顯式地作為獨立的結構來表示. 93.圖的邊經常存儲為動態分配的數組或鏈表, 在這兩種情況下, 邊都錨定在圖的結點上. 94.在無向圖中, 表達數據時應該將所有的結點看作是等同的, 類似地, 進行處理任務的代碼也不應該基於它們的方向來區分邊. 95.在非連通圖中, 執行遍歷代碼應該能夠接通孤立的子圖. 96.處理包含迴路的圖時, 遍歷代碼應該避免在處理圖的迴路進入循環. 97.復雜的圖結構中, 可能隱藏著其他類型的獨立結構. +++++++++++++++++ 第五章: 高級控制流程 +++++++++++++++++ 98.採用遞歸定義的演算法和數據結構經常用遞歸的函數定義來實現. 99.推理遞歸函數時, 要從基準落伍測試開始, 並認證每次遞歸調用如何逐漸接近非遞歸基準範例代碼. 100.簡單的語言常常使用一系列遵循該語言語法結構的函數進行語法分析. 101.推理互遞歸函數時, 要基於底層概念的遞歸定義. 102.尾遞歸調用等同於一個回到函數開始處的循環. 103.將throws子句從方法的定義中移除, 然後運行Java編譯器對類的源代碼進行編譯, 就可以容易地找到那些可能隱式地生成異常的方法. 104.在多處理器計算機上運行的代碼常常圍繞進程或線程進行組織. 105.工作群並行模型用於在多個處理器間分配工作, 或者創建一個任務池, 然後將大量需要處理標准化的工作進行分配. 106.基於線程的管理者/工人並行模型一般將耗時的或阻塞的操作分配給工人子任務, 從而維護中心任務的響應性. 107.基於進程的管理者/工人並行模型一般用來重用現有的程序, 或用定義良好的介面組織和分離粗粒度的系統模塊. 108.基於流水線的並行處理中, 每個任務都接收到一些輸入, 對它們進行一些處理, 並將生成的輸出傳遞給下一個任務, 進行不同的處理. 109.競爭條件很難捉摸, 相關的代碼常常會將競爭條件擴散到多個函數或模塊; 因而, 很難隔離由於競爭條件導致的問題. 110.對於出現在信號處理器中的數據結構操作代碼和庫調用要保持高度警惕. 111.在閱讀包含宏的代碼時, 要注意, 宏既非函數, 也非語句. 112.do…while(0)塊中的宏等同於控制塊中的語句. 113.宏可以訪問在它的使用點可見的所有局部變數. 114.宏調用可改變參數的值 115.基於宏的標記拼接能夠創建新的標記符. +++++++++++++++++ 第六章: 應對大型項目 +++++++++++++++++ 116.我們可以通過瀏覽項目的源代碼樹—包含項目源代碼的層次目錄結構, 來分析一個項目的組織方式. 源碼樹常常能夠反映出項目在構架和軟體過程上的結構. 117.應用程序的源代碼樹經常是該應用程序的部署結構的鏡像. 118.不要被龐大的源代碼集合嚇倒; 它們一般比小型的專門項目組織得更出色. 119.當您首次接觸一個大型項目時, 要花一些時間來熟悉項目的目錄樹結構. 120.項目的源代碼遠不只是編譯後可以獲得可執行程序的計算機語言指令; 一個項目的源碼樹一般還包括規格說明|最終用戶和開發人員文檔|測試腳本|多媒體資源|編譯工具|例子|本地化文件|修訂歷史|安裝過程和許可信息. 121.大型項目的編譯過程一般聲明性地藉助依賴關系來說明. 依賴關系由工具程序, 如make及其派生程序, 轉換成具體的編譯行動. 122.大型項目中, 製作文件常常由配置步驟動態地生成; 在分析製作文件之前, 需要先執行項目特定的配置. 123.檢查大型編譯過程的各個步驟時, 可以使用make程序的-n開關進行預演. 124.修訂控制系統提供從儲存庫中獲取源代碼最新版本的方式. 125.可以使用相關的命令, 顯示可執行文件中的修訂標識關鍵字, 從而將可執行文件與它的源代碼匹配起來. 126.使用修訂日誌中出現的bug跟蹤系統內的編號, 可以在bug跟蹤系統的資料庫中找到有關的問題的說明. 127.可以使用修訂控制系統的版本儲存庫, 找出特定的變更是如何實現的. 128.定製編譯工具用在軟體開發過程的許多方面, 包括配置|編譯過程管理|代碼的生成|測試和文檔編制. 129.程序的調試輸出可以幫助我們理解程序控制流程和數據元素的關鍵部分. 130.跟蹤語句所在的地點一般也是演算法運行的重要部分. 131.可以用斷言來檢驗演算法運作的步驟|函數接收的參數|程序的控制流程|底層硬體的屬性和測試用例的結果. 132.可以使用對演算法進行檢驗的斷言來證實您對演算法運作的理解, 或將它作為推理的起點. 133.對函數參數和結果的斷言經常記錄了函數的前置條件和後置條件. 134.我們可以將測試整個函數的斷言作為每個給定函數的規格說明. 135.測試用例可以部分地代替函數規格說明. 136.可以使用測試用例的輸入數據對源代碼序列進行預演. +++++++++++++++++++ 第七章: 編碼規范和約定 +++++++++++++++++++ 137.了解了給定代碼庫所遵循的文件組織方式後, 就能更有效率地瀏覽它的源代碼. 138.閱讀代碼時, 首先要確保您的編輯器或優美列印程序的tab設置, 與代碼遵循的風格規范一致. 139.可以使用代碼塊的縮進, 快速地掌握代碼的總體結構. 140.對編排不一致的代碼, 應該立即給予足夠的警惕. 141.分析代碼時, 對標記為XXX, FIXME和TODO的代碼序列要格外注意: 錯誤可能就潛伏在其中. 142.常量使用大寫字母命名, 單詞用下劃線分隔. 143.在遵循Java編碼規范的程序中, 包名(package name)總是從一個頂級的域名開始(例如, org, com), 類名和介面名由大寫字母開始, 方法和變數名由小寫字母開始. 144.用戶界面控制項名稱之前的匈牙利記法的前綴類型標記可以幫助我們確定它的作用. 145.不同的編程規范對可移植構造的構成有不同的主張. 146.在審查代碼的可移植性, 或以某種給定的編碼規范作為指南時, 要注意了解規范對可移植性需求的界定與限制. 147.如果GUI功能都使用相應的編程結構來實現, 則通過代碼審查可以輕易地驗證給定用戶界面的規格說明是否被正確地採用. 148.了解項目編譯過程的組織方式與自動化方式之後, 我們就能夠快速地閱讀與理解對應的編譯規則. 149.當檢查系統的發布過程時, 常常可以將相應發行格式的需求作為基準. ++++++++++++ 第八章: 文檔 ++++++++++++ 150.閱讀代碼時, 應該盡可能地利用任何能夠得到的文檔. 151.閱讀一小時代碼所得到的信息只不過相當於閱讀一分鍾文檔. 152.使用系統的規格說明文檔, 了解所閱讀代碼的運行環境. 153.軟體需求規格說明是閱讀和評估代碼的基準. 154.可以將系統的設計規格說明作為認知代碼結構的路線圖, 閱讀具體代碼的指引. 155.測試規格說明文檔為我們提供可以用來對代碼進行預演的數據. 156.在接觸一個未知系統時, 功能性的描述和用戶指南可以提供重要的背景信息,從而更好地理解閱讀的代碼所處的上下文. 157.從用戶參考手冊中, 我們可以快速地獲取, 應用程序在外觀與邏輯上的背景知識, 從管理員手冊中可以得知代碼的介面|文件格式和錯誤消息的詳細信息. 158.利用文檔可以快捷地獲取系統的概況, 了解提供特定特性的代碼. 159.文檔經常能夠反映和提示出系統的底層結構. 160.文檔有助於理解復雜的演算法和數據結構. 161.演算法的文字描述能夠使不透明(晦澀, 難以理解)的代碼變得可以理解. 162.文檔常常能夠闡明源代碼中標識符的含義. 163.文檔能夠提供非功能性需求背後的理論基礎. 164.文檔還會說明內部編程介面. 165.由於文檔很少像實際的程序代碼那樣進行測試, 並受人關注, 所以它常常可能存在錯誤|不完整或過時. 166.文檔也提供測試用例, 以及實際應用的例子. 167.文檔常常還會包括已知的實現問題或bug. 168.環境中已知的缺點一般都會記錄在源代碼中. 169.文檔的變更能夠標出那些故障點. 170.對同一段源代碼重復或互相沖突的更改, 常常表示存在根本性的設計缺陷, 從而使得維護人員需要用一系列的修補程序來修復. 171.相似的修復應用到源代碼的不同部分, 常常表示一種易犯的錯誤或疏忽, 它們同樣可能會在其他地方存在. 172.文檔常常會提供不恰當的信息, 誤導我們對源代碼的理解. 173.要警惕那些未歸檔的特性: 將每個實例歸類為合理|疏忽或有害, 相應地決定是否應該修復代碼或文檔. 174.有時, 文檔在描述系統時, 並非按照已完成的實現, 而是系統應該的樣子或將來的實現. 175.在源代碼文檔中, 單詞gork的意思一般是指」理解」. 176.如果未知的或特殊用法的單詞阻礙了對代碼的理解, 可以試著在文檔的術語表(如果存在的話)|New Hacker』s Dictionary[Ray96]|或在Web搜索引擎中查找它們. 177.總是要以批判的態度來看待文檔, 注意非傳統的來源, 比如注釋|標准|出版物|測試用例|郵件列表|新聞組|修訂日誌|問題跟蹤資料庫|營銷材料|源代碼本身. 178.總是要以批判的態度來看待文檔; 由於文檔永遠不會執行, 對文檔的測試和正式復查也很少達到對代碼的同樣水平, 所以文檔常常會誤導讀者, 或者完全錯誤. 179.對於那些有缺陷的代碼, 我們可以從中推斷出它的真實意圖. 180.在閱讀大型系統的文檔時, 首先要熟悉文檔的總體結構和約定. 181.在對付體積龐大的文檔時, 可以使用工具, 或將文本輸出到高品質輸出設備上, 比如激光列印機, 來提高閱讀的效率. ++++++++++++++ 第九章: 系統構架 ++++++++++++++ 182.一個系統可以(在重大的系統中也確實如此)同時出多種不同的構架類型. 以不同的方式檢查同一系統|分析系統的不同部分|或使用不同級別的分解, 都有可能發現不同的構架類型. 183.協同式的應用程序, 或者需要協同訪問共享信息或資源的半自治進程, 一般會採用集中式儲存庫構架. 184.黑板系統使用集中式的儲存庫, 存儲非結構化的鍵/值對, 作為大量不同代碼元件之間的通信集線器. 185.當處理過程可以建模|設計和實現成一系列的數據變換時, 常常會使用數據流(或管道—過濾器)構架. 186.在批量進行自動數據處理的環境中, 經常會採用數據流構架, 在對數據工具提供大量支持的平台上尤其如此. 187.數據流構架的一個明顯徵兆是: 程序中使用臨時文件或流水線(pipeline)在不同進程間進行通信. 188.使用圖示來建模面向對象構架中類的關系. 189.可以將源代碼輸入到建模工具中, 逆向推導出系統的構架. 190.擁有大量同級子系統的系統, 常常按照分層構架進行組織. 191.分層構架一般通過堆疊擁有標准化介面的軟體組件來實現. 192.系統中每個層可以將下面的層看作抽象實體, 並且(只要該層滿足它的需求說明)不關心上面的層如何使用它. 193.層的介面既可以是支持特定概念的互補函數族, 也可以是一系列支持同一抽象介面不同底層實現的可互換函數. 194.用C語言實現的系統, 常常用函數指針的數組, 表達層介面的多路復用操作. 195.用面向對象的語言實現的系統, 使用虛方法調用直接表達對層介面的多嘴復用操作. 196.系統可以使用不同的|獨特的層次分解模型跨各種坐標軸進行組織. 197.使用程序切片技術, 可以將程序中的數據和控制之間依賴關系集中到一起. 198.在並發系統中, 一個單獨的系統組件起到集中式管理器的作用, 負責啟動|停止和協調其他系統進程和任務的執行. 199.許多現實的系統都會博採眾家之長. 當處理此類系統時, 不要徒勞地尋找無所不包的構架圖; 應該將不同構架風格作為獨立但相關的實體 來進行定位|識別並了解. 200.狀態變遷圖常常有助於理清狀態機的動作. 201.在處理大量的代碼時, 了解將代碼分解成單獨單元的機制極為重要. 202.大多數情況下, 模塊的物理邊界是單個文件|組織到一個目錄中的多個文件或擁有統一前綴的文件的集合. 203.C中的模塊, 由提供模塊公開介面的頭文件和提供對應實現的源文件組成. 204.對象的構造函數經常用來分配與對象相關的資源, 並初始化對象的狀態. 函數一般用來釋放對象在生命期中佔用的資源. 205.對象方法經常使用類欄位來存儲控制所有方法運作的數據(比如查找表或字典)或維護類運作的狀態信息(例如, 賦給每個對象一個標識符的 計數器). 206.在設計良好的類中, 所有的欄位都應在聲明為private, 並用公開的訪問方法提供對它們的訪問. 207.在遇到friend聲明時, 要停下來分析一下, 看看繞過類封裝在設計上的理由. 208.可以有節制地用運算符增強特定類的可用性, 但用運算符重載, 將類實現為擁有內建算術類型相關的全部功能的類實體, 是不恰當的. 209.泛型實現不是在編譯期間通過宏替換或語言所支持的功能(比如C++模板和Ada的泛型包)來實現, 就是在運行期間通過使用數據元素的指針和函數的指針|或對象的多態性實現. 210.抽象數據類型經常用來封裝常用的數據組織方案(比如樹|列表或棧), 或者對用戶隱藏數據類型的實現細節. 211.使用庫的目的多種多樣: 重用源代碼或目標代碼, 組織模塊集合, 組織和優化編譯過程, 或是用來實現應用程序各種特性的按需載入. 212.大型的|分布式的系統經常實現為許多互相協作的進程. 213.對於基於文本的數據儲存庫, 可以通過瀏覽存儲在其中的數據, 破譯出它的結構. 214.可以通過查詢數據字典中的表, 或使用資料庫專有的SQL命令, 比如show table, 來分析關系型資料庫的模式. 215.識別出重用的構架元素後, 可以查找其最初的描述, 了解正確地使用這種構架的方式, 以及可能出現的誤用. 216.要詳細分析建立在某種框架之上的應用程序, 行動的最佳路線就是從研究框架自身開始. 217.在閱讀向導生成的代碼時, 不要期望太高, 否則您會感到失望. 218.學習幾個基本的設計模式之後, 您會發現, 您查看代碼構架的方式會發生改變: 您的視野和詞彙將會擴展到能夠識別和描述許多通用的形式. 219.頻繁使用的一些模式, 但並不顯式地指出它們的名稱, 這是由於構架性設計的重用經常先於模式的形成. 220.請試著按照底層模式來理解構架, 即使代碼中並沒有明確地提及模式. 221.大多數解釋器都遵循類似的處理構架, 圍繞一個狀態機進行構建, 狀態機的操作依賴於解釋器的當前狀態|程序指令和程序狀態. 222.多數情況下, 參考構架只是為應用程序域指定一種概念性的結構, 具體的實現並非必須遵照這種結構. +++++++++++++++++ 第十章: 代碼閱讀工具 +++++++++++++++++ 223.詞彙工具可以高效地在一個大代碼文件中或者跨多個文件查找某種模式. 224.使用程序編輯器和正則表達式查找命令, 瀏覽龐大的源代碼文件. 225.以只讀方式瀏覽源代碼文件. 226.使用正則表達式 ^function name 可以找出函數的定義. 227.使用正則表達式的字元類, 可以查找名稱遵循特定模式的變數. 228.使用正則表達式的否定字元類, 可以避免非積極匹配. 229.使用正則表達式 symbol-1. *symbol-2, 可以查找出現在同一行的符號. 230.使用編輯器的 tags 功能, 可以快速地找出實體的定義. 231.可以用特定的 tag 創建工具, 增加編輯器的瀏覽功能. 232.使用編輯器的大綱視圖, 可以獲得源代碼結構的鳥瞰圖. 233.使用您的編輯器來檢測源代碼中圓括弧|方括弧和花括弧的匹配. 234.使用 grep 跨多個文件查找代碼模式. 235.使用 grep 定位符號的聲明|定義和應用. 236.當您不能精確地表述要查找的內容時, 請使用關鍵單詞的詞干對程序的源代碼進行查找. 237.用 grep 過濾其他工具生成的輸出, 分離出您要查找的項. 238.將 grep 的輸出輸送到其他工具, 使復雜處理任務自動化. 239.通過對 grep 的輸出進行流編輯, 重用代碼查找的結果. 240.通過選取與噪音模式不匹配的輸出行(grep-v), 過濾虛假的 grep 輸出. 241.使用 fgrep 在源代碼中查找字元串列表. 242.查找注釋, 或標識符大小寫不敏感的語言編寫的代碼時, 要使用大小寫不敏感的模式匹配(grep -i). 243.使用 grep –n 命令行開關, 可以創建與給定正則表達式匹配的文件和行號的檢查表. 244.可以使用 diff 比較文件或程序不同版本之間的差別. 245.在運行 diff 命令時, 可以使用 diff –b, 使文件比較演算法忽略結尾的空格, 用–w 忽略所有空白區域的差異, 用–i 使文件比較對大小寫不敏感. 246.不要對創建自己的代碼閱讀工具心存畏懼. 247.在構建自己的代碼閱讀工具時: 要充分利用現代快速原型語言所提供的能力; 從簡單開始, 根據需要逐漸改進; 使用利用代碼詞彙結構的各種試探法; 要允許一些輸出噪音或寂靜(無關輸出或缺失輸出); 使用其他工具對輸入進行預處理, 或者對輸出進行後期處理. 248.要使編譯器成為您的: 指定恰當級別的編譯器警告, 並小心地評估生成的結果. 249.使用C預處理器理清那些濫用預處理器特性的程序. 250.要徹底地了解編譯器如何處理特定的代碼塊, 需要查看生成的符號(匯編)代碼. 251.通過分析相應目標文件中的符號, 可以清晰地了解源文件的輸入和輸出. 252.使用源代碼瀏覽器瀏覽大型的代碼集合以及對象類型. 253.要抵制住按照您的編碼規范對外部代碼進行美化的誘惑; 不必要的編排更改會創建不同的代碼, 並妨礙工作的組織. 254.優美列印程序和編輯器語法著色可以使得程序的源代碼為易讀. 255.cdecl 程序可以將難以理解的C和C++類型聲明轉換成純英語(反之亦然). 256.實際運行程序, 往往可以更深刻地理解程序的動作. 257.系統調用|事件和數據包跟蹤程序可以增進對程序動作的理解. 258.執行剖析器可以找出需要著重優化的代碼, 驗證輸入數據的覆蓋性, 以及分析演算法的動作. 259.通過檢查從未執行的代碼行, 可以找出測試覆蓋的弱點, 並據此修正測試數據. 260.要探究程序動態動作時的每個細節, 需要在調試器中運作它. 261.將您覺得難以理解的代碼列印到紙上. 262.可以繪制圖示來描繪代碼的動作. 263.可以試著向別人介紹您在閱讀的代碼, 這樣做
Ⅳ 如何閱讀源代碼
一個大項目的源代碼,不要過份詳細的閱讀。大項目,其代碼量基本上是可以嚇死人的。過份的關注細節,常常會拘泥於細節,而忽略了整體框架。當你能夠看清框架的時候,亦花費了太多的時間。
因此,閱讀一個大項目的源代碼,其目的不在於欣賞代碼細節,而在於迅速看清項目整體框架的大概面貌:都有那些模塊,這些模塊是幹嘛的(不關心具體怎麼干),模塊之間的通訊機制大概是怎樣的,然後在考慮子模塊,通常只要掌握兩級子模塊就夠了。花上1,2天的時間掌握這一切,就達到了閱讀大項目源碼的目的。因為一旦你掌握了框架,你就可以按照這個框架實現這個項目,雖然和原項目全然不同,但是完成的需求卻是一樣的。
在軟體中,架構才是本質。
也許你指望詳細閱讀大項目源代碼能看到高質量的代碼,但是,大項目通常都是團隊的勞動成果,每個人的不同水平造就了代碼質量的高高低低,一個人在不同時間不同環境的代碼質量也是不同的。要指望在大片源碼面前找到高質量,簡直是天方夜譚。
也許你要從閱讀源碼中掌握某項技術細節,比如bsp,又或者換裝,那麼,最好的建議是查找相關的技術文檔以及文檔上所附帶的sample code,這種sample code一般不會附帶任何干擾,簡潔得只是為了證明該技術而存在的。如果沒有這些東西,而只能從大項目源碼中找的話,你提前先了解了框架,能更快的查找和定位到表達該技術的文件。但是通常都會比較不幸,因為你為了明白這一技術,通常要先理解混入其中的另一技術。
最後談談怎樣才能閱讀到高質量的源代碼。何謂高質量?是指演算法出人一表(比如某種o(1)的排序法)?還是採用了極端深奧的語言特性將某實現完美表達(比如模板的靈活運用)?無論是哪種,最好的來源是書,如《STL詳解》,或者《inside XX》這樣的東西。書的作者通常就是這些高質量代碼的作者,他會帶領你探索這些源碼背後的真相。
Ⅳ 如何高效閱讀源代碼
下面是之前寫的一篇文章:《如何快速閱讀源碼》
本文探討在需要了解一個開源項目時,如何快速的理清開源項目的代碼邏輯!
以下是個人認為行之有效的方法:
本文以Mybatis為例來進行演示!
先「跑起來」程序界有個老傳統,學習新技術時都是從「Hello World」開始的!無論是學習新語言時,列印「Hello World」;還是學習新框架時編寫個demo!那為什麼這里的「跑起來」要打個引號呢?
實際上,當你想要閱讀一個開源項目的源碼時,絕大部分情況下,你已經能夠使用這個開源項目了!所以這里的「跑起來」就不是寫個「Hello World」,也不是能跑起來的程序了!而是能__在你的腦子里「跑起來」__!什麼意思?
Mybatis你會用了吧?那麼請問Mybatis是如何執行的呢?仔細想想,你能否用完整的語句把它描述出來?
這里是Mybatis的官方入門文章!你是如何看這篇文章的?讀一遍就行了嗎?還是跟著文章跑一遍就夠了嗎?從這篇文章里你能獲得多少信息?
我們來理一下:
回答出了上面這些問題!你也就基本能在腦子里把Mybatis「跑起來」了!之後,你才能正真的開始閱讀源碼!
當你能把一個開源項目「跑起來」後,實際上你就有了對開源項目最初步的了解了!就像「 書的索引 」一樣!基於這個索引,我們一步步的進行拆解,來細化出下一層的結構和流程,期間可能需要深入技術細節,考量實現,考慮是否有更好的實現方案!也就是說後面的三步並不是線性的,而是__不斷交替執行__的一個過程!最終就形成一個完整的源碼執行流程!
自頂向下拆解繼續通過Mybatis來演示(限於篇幅,我只演示一個大概流程)!我們現在已經有了一個大概的流程了:
雖說每個點都可以往下細化,但是也分個輕重緩急!
很明顯,SqlSession去執行 sql才是Mybatis的核心!我們先從這個點入手!
首先,你當然得先下載Mybatis的源碼了(請自行下載)!
我們直接去看SqlSession!它是個介面,裡面有一堆執行sql的方法!
這里只列出了一部分方法:
SqlSession就是通過這些方法來執行sql的!我們直接看我們常用的,也是Mybatis推薦的用法,就是基於Mapper的執行!也就是說「SqlSession通過Mapper來執行具體的sql」!上面的流程也就細化成了:
那SqlSession是如何獲取Mapper的呢?Mapper又是如何執行sql的呢?
深入細節我們來看SqlSession的實現!SqlSession有兩個實現類SqlSessionManager和DefaultSqlSession!通過IDE的引用功能可以查看兩個類的使用情況。你會發現SqlSessionManager實際並沒有使用!而DefaultSqlSession是通過DefaultSqlSessionFactory構建的!所以我們來看DefaultSqlSession是如何構建Mapper的!
它直接委託給了Configuration的getMapper方法!
Configuration又委託給了MapperRegistry類的getMapper方法!
在MapperRegistry類的getMapper中:
在這里knowMappers是什麼?MapperProxyFactory又是什麼?mapperProxyFactory.newInstance(sqlSession)具體做了什麼?
其實很簡單,knowMappers是個Map,裡麵包含了class與對應的MapperProxyFactory的對應關系!MapperProxyFactory通過newInstance來構建對應的Mapper(實際上是Mapper的代理)!
快接近真相了,看mapperProxyFactory.newInstance(sqlSession)里的代碼:
這里幹了什麼?
最終實際還是委託給了sqlSession去執行具體的sql!後面具體怎麼實現的就自行查看吧!
延伸改進現在我們的流程大概是這樣的一個過程:
現在我們大概知道了:
那麼,
這個問題列表可以很長,可以按個人需要去思考並嘗試回答!可能最終這些問題已經和開源項目本身沒有什麼關系了!但是你思考後的收獲要比看源碼本身要多得多!
再循環一輪結束後,可以再次進行:
不斷的拆解->深入->改進,最終你能__通過一個開源項目,學習到遠比開源項目本身多得多的知識__!
最重要的是,你的流程是完整的。無論是最初的大致流程:
還是到最終深入的細枝末節,都是個完整的流程!
這樣的好處是,你的時間能自由控制:
而不像debug那樣的方式,需要一下子花費很長的時間去一步步的理流程,費時費力、收效很小,而且如果中斷了就很難繼續了!
總結本文通過梳理Mybatis源碼的一個簡單流程,來講述一個個人認為比較好的閱讀源碼的方式,並闡述此方法與傳統debug方式相比的優勢。
閱讀源碼是每個優秀開發工程師的必經之路,那麼這篇文章就來講解下為什麼要閱讀源碼以及如何閱讀源碼。
首先來說下為什麼要讀源碼,有學習源碼的必要嗎?
為什麼要閱讀源碼?
關於為什麼閱讀和學習源碼,我個人認為可能有以下幾點:
(一)吊打面試官,應對面試
為了找到更好的工作,應對面試,因為在面試中肯定會問到源碼級別的問題,比如:為什麼 HashMap 是線程不安全的?
如果你沒有閱讀過源碼,面試官可能會對回答的結果不滿意,進而導致面試結果不太理想,但如果你對源碼有所研究,並能夠很好地問答面試官的問題,這可能就是你的加分點,可以形成自己獨特的競爭力,吊打面試官,升職加薪不是夢。
(二)解決問題(bug)
在開發過程中,我們或多或少會遇到 bug,比如:在 foreach 循環里進行元素的 remove/add 操作,為啥有可能會報 異常?
我們可以先在 Google、Stack Overflow 以及對應項目的 Issues 里看有沒有類似問題以及解決辦法,如果沒有的話,我們只能通過閱讀源碼的方式去解決了。如果我們對相關源碼有所涉獵,就可以快速定位到問題所在。
(三)提升編程能力
和閱讀一本好書一樣,閱讀源碼就是和編程大牛面對面交流的機會,在許多優秀的開源項目中,它們的編碼規范和架構設計都是很棒的,另外在設計上也使用了大量的設計模式,通過閱讀和學習源碼,能夠快速提升我們的編碼水平,以及對設計模式有更深的理解。
同時,在我們閱讀完一個源碼後,可以觸類旁通,能夠快速地對其他框架的源碼進行閱讀和學習,減少時間成本。
除了上述提到的原因之外,可能還有許多,在這里就不一一贅述了,那麼在確定了要閱讀源碼之後,就讓我們看下如何閱讀源碼吧!
如何閱讀源碼?
如何閱讀源碼取決於你為什麼要讀源碼,比如:
下面大概說下閱讀源碼的幾點建議:
在閱讀之前,可以先從開源項目的官網上看它的架構設計和功能文檔,了解這個項目的 整體架構、模塊組成以及各個模塊之間的聯系 。
如果沒有對應的項目文檔,可以根據代碼的模塊進行梳理,以形成對項目的初步了解,或者 查看已有的源碼解析文章或者書籍 ,在閱讀源碼之前,了解項目的架構和思路會使閱讀源碼事半功倍。
在了解一個類的時候,可以使用 ctrl+F12 來查看類中的成員變數和方法。
可以通過 IDEA 的 Diagrams 功能去了解一個類的繼承關系。
多打 斷點調試 ,斷點追蹤源碼是很好的閱讀源碼的方式,可以先通過 debug 了解下調用邏輯,都和哪些類有關聯,有大致了解後再通過 debug 了解整體代碼的功能實現,各個類都起到了什麼作用,有沒有涉及到設計模式等。
另外,優秀的開源項目中肯定會有許多地方應用到了 設計模式 ,建議在閱讀源碼之前,需要對常用的設計模式有大致的了解,不然閱讀源碼的效率會大大降低。
如果遇到讀不懂某部分源碼的時候,可以先跳過,之後再回來看,如果屬於搞不懂這部分就茶不思飯不想的人,可以在網上找是否有該部分源碼的解析或者文檔,也可以自己通過 源碼注釋和測試用例 去閱讀學習。
一般優秀的開源項目都會有 單元測試 ,可以通過對應類的單元測試去了解方法的含義和用法,加深對源碼邏輯的理解。
在閱讀源碼的時候,可以在代碼上加上 注釋和總結 ,同時還可以畫出 時序圖和類圖 ,這樣對閱讀源碼有很大的幫助,可以很清楚地知道類之間的調用關系和依賴關系,也方便以後回顧,重新閱讀。
在這里推薦大家一個 IDEA 插件 SequenceDiagram,可以根據源碼生成調用時序圖,便於閱讀源碼。
剛開始閱讀源碼,不建議直接看框架源碼,可以先從 jdk 源碼看起:
jdk 源碼也是非常龐大的,可以分模塊來閱讀,下面是建議的閱讀順序:
其他包下的代碼也可以做下了解,JDK源碼閱讀筆記:https://github.com/wupeixuan/JDKSourceCode1.8
再有了一定的源碼閱讀經驗後,可以再去學習 Spring、Spring Boot、Dubbo、Spring Cloud 等框架的源碼。
總結主要介紹了為什麼讀源碼以及如何讀源碼,供大家參考,每個人都有適合自己的閱讀源碼的方式,希望可以在學習中去摸索出一套屬於自己的方式。
閱讀源碼不是一蹴而就的,這是持久戰,只要你能夠堅持下來,肯定受益匪淺。閱讀源碼的過程比較枯燥,可以在社群里一起討論學習,這樣可能效率更高些。
沒看過源代碼,都不好意思出來說了,最近剛好在看一些,來說一個。
先看使用 https://element.eleme.cn/#/zh-CN/component/installation
先看一下這個庫是做什麼用的,然後提供了哪些功能。
看GitHub https://github.com/elemefe
一般會看下項目最新的情況,然後沒有關閉的issue,看下wiki,大家在討論什麼。
再看代碼
clone 一份到本地,然後先看下目錄結構,然後根據文檔看幾個簡單的組件的時候,一邊看掘金上的分析,一邊自己看下實現。
e le
餓了么這個框架代碼結構還是很清楚的,基本上每個組件都是分開的,所以你只要看其他的一個文件夾就行。然後一些工具的都在src文件夾。
要學會看issue,一般開源的項目都有人會來提建議,有些是bug,有些是功能,你可以看看自己是否有能力去解決,如果可以的話,你可以去fork代碼,然後自己修改,再提pr。
我最近恰好找摸索出一個梳理遺留系統架構的技巧:自底向上 找到一個典型的切面 沿著調用和回調的路徑 在代碼中添加結構化注釋(比如eclipse中加//TAG 流程A1.1 甲->>乙),這樣便得到了一個code地圖,並且在tasks視圖中看起來很直觀(看起來跟書的目錄一樣)可快速跳轉。將目錄到有道雲筆記的markdown序列圖中 就自動生成了一個序列圖。
我覺得這基本上就是可縮放的可視化架構地圖了,對維護一個比較亂和龐大的遺留系統非常有幫助,定位代碼 修改維護都方便多了。
1、需要過硬的基礎知識,這個前提。不然基本語法、常用的模式都不曉得怎麼讀。
2、多參考 歷史 版本和更新變化,好的源碼都是反復迭代出來的精華,開始就讀精華是很不明智的,所以看看版本更新說明,版本的 歷史 演變。就想人一樣是怎樣進化過來的。
3、參考別人閱讀注釋,想必在你讀源碼之前也有人讀過了源碼,並且總結,注釋。和分享原理,可供你參考,畢竟每個人讀一篇文章,理解的東西是有差異化的。
4、直接買書,有些作品直接出書就是源碼精解
5、找個大神給你慢慢分析,這個最快。娓娓道來,直接面授比啥都強。缺點是,你容易跟著他的思維走下去。
我覺得閱讀代碼就不應該高效,而應該像看小說一樣,看的過程就像是在和作者交流,有趣才是看代碼的動力。
畫圖,看數據走向,邏輯走向
先弄清楚這些代碼實現了哪些功能,然後從主線開始往下看,好的代碼光看變數和介面名稱就能明白是什麼意思?扒出源碼實現的整體框架邏輯,然後再對自己感興趣的模塊進行剖析,還是從整體把握,細節深入,慢慢地整個框架就被豐滿了。
接下來是思考為什麼要如此設計,這樣設計的好處是什麼?如果是你來做應該怎麼設計,把你覺得源碼缺點的地方進行仔細研究,了解裡面是否包含自己不清楚的細節,避免遺漏。
接下來就是根據代碼改造或者是調試錯誤,對於源碼中遇到的不理解的地方一定要弄明白,有的確實是畫蛇添足,有的有獨特的作用。
多多學習,對每一種主流框架銘記於心,對主流設計模式了如指掌,萬變不離其宗,源碼看多了,跟看一個電視機遙控器的操作說明一樣。
1、一邊閱讀代碼一邊寫注釋。這是我用過的最好的方法,對代碼理解得更深入,看一些重要代碼或者特別難懂的代碼時挺有用。更何況,注釋也是一種文檔嘛。
2、一邊閱讀代碼一邊繪制UML。這個方法適用於類之間的關系較復雜和調用層次較深的情況,我一般都是先繪制順序圖,然後為順序圖中的類繪制關系圖。
3、通過Debug來跟蹤程序的主要執行過程,這樣就可以分清主次了,閱讀的時候更有針對性。
4、類的快速閱讀。先弄清楚它在繼承鏈中的位置,看看它的內部狀態,也就是成員變數,一般來說,類的對外介面都是對成員變數的訪問、加工、代理等,然後看看它的對外介面,也就是公有成員函數,識別核心的一個或多個函數,這時候你應該可以大概了解這個類的職責或作用了。可能這個類是某個設計模式中的一個組成部分,所以,設計模式的掌握對代碼的快速閱讀也是很有幫助的。
5、帶著問題去閱讀。比如想了解android中的消息機制,那麼看看Looper、Handler、MessegeQueue這幾個類就可以了,其他的不要去看,要不然就跑題了。
下面列幾個閱讀源碼時所處的情景,在特定場景下用哪些方法: 不太熟悉業務邏輯,還不是很清楚它是幹啥的,可以用3、5。 代碼量很大,有幾十萬行,甚至百萬行,可以用2、3、5。 你無法看見程序的運行過程,比如沒有用戶界面,也有可能是無法運行的,可以用3、5。 設計復雜,用了大量的設計模式,調用鏈很深,可以用1、2、3、4、5。 時間有限,沒有那麼多時間讓你看源碼,可以用3、5。
畫出邏輯流程圖,先了解整體流程,再詳解具體函數
Ⅵ JAVA閱讀源碼,大量英文注釋閱讀不方便,求集成idea裡面的翻譯java注釋由英文翻譯為中文的工具。
學會在idea(eclipse)中閱讀、調試源碼,是java程序員必不可少的一項技能。
在idea中配完環境後,默認其實也是能夠對jdk的源碼進行debug調試的。但是無法在源碼中添加自己的注釋,無法添加自己的理解。如果乾瞪眼看的話,可能過段時間,就忘記了。下面就介紹下,如何在jdk源碼中為所欲為,像在我們自己的代碼中一樣寫注釋、調代碼:
打開idea,選擇Project->File->Project Structure->SDKs->Sourcepath,初始狀態如下圖 :
這時,再重新打開jdk的源碼類,我們就可以在源java文件中,添加自己的注釋了。
一定注意:添加註釋時,一定不要新加一行寫注釋。最好在一行代碼的後面,使用//進行注釋。否則行號和真正的jre中編譯後的代碼行號對應不上,如果對源碼debug時,會出現代碼運行和行號不匹配的情況
Ⅶ 如何查看大型工程的源代碼
程序員在工作過程中,會遇到很多需要閱讀源碼的場景,比如技術預研、選擇技術框架、接手以前的項目、review他人的代碼、維護老產品等等。可以說,閱讀源代碼是程序員的基本功,這項基本功是否扎實,會在很大程度上影響一個程序員在技術上的成長速度。2014年寫《Qt on Android核心編程》和《Qt Quick核心編程》時,很多內容都是通過分析Qt源碼搞明白的。這陣子研究CEF和PPAPI,也主要靠研究源代碼來搞明白用法。最近工作上要修改已有項目的一個子系統,也是得硬著頭皮先讀懂代碼。總之在開發工作這十來年中,讀過太多源碼了,從源代碼中學習到太多東西了,如果不閱讀源代碼,真不知道自己能否成長起來。寫代碼是從模仿開始的,提高也是從觀摩別人的優秀設計和代碼開始的。所以閱讀源碼至關重要,接下來咱從下列方面聊聊閱讀源碼的事兒。不同的目的會有不同的心情,會影響到工作的進展,像修復他人的Bug這種事情,類似於沒被掰彎的男猿捏著鼻子給另外一個男人擦屁股,是很惡心的,很容易讓人拒絕的。所以因這種目標而閱讀源碼,往往是欲拒還迎、欲說還休,效率較低。然而修復實際工作中幫別人修復Bug這種情形,十有八九你要遇到,無可逃避。所以,心理調試很重要。為了學習去讀源碼,這是最愉快的最放鬆的。不過提醒一點,設定可檢驗的目標才會有收獲,否則就會像走到大街上看見一美女擦肩而過那樣,驚艷一下下,過後嘛關系嘛收獲也沒了。其他的目的,重構舊代碼、添加新功能,比幫別人擦溝子(陝西話,屁股)略強,因為他帶有創造性,創造性的活動能給人帶來強烈的愉悅,所以雖然這兩種目的也有很多讓人不爽的部分,不過想到我可以讓一棵老樹煥發青春,不爽也就慢慢弱下去了。
Ⅷ 在學習web想知道如何看懂網頁源代碼,有什麼好的方法嗎,或者有網頁源代碼旁邊有詳細備注的,這樣上手
第一種:打開一個網頁後點擊滑鼠的 右鍵就會有"查看源文件",操作 滑鼠右鍵--->查看源文件 即可彈出一個記事本,而記事本內容就是此網頁的html代碼。
可能會碰到一些網頁滑鼠右鍵無反應或提出提示框,那是因為做網頁的加入了JS代碼來禁止用戶查看源文件代碼或復制網頁內容,但是這種方法也沒用,只有你稍微懂得以下第二種方法即可查看此網頁的源代碼源文件。
第二種:通過瀏覽器狀態欄或工具欄中的點擊 「查看」然後就用一項「查看源代碼」,點擊查看源代碼即可查看此網頁的源代碼源文件。
在微軟IE下 查看--->源文件 即可查看此網頁代碼在傲遊瀏覽器下截圖:
查看別人網頁的源代碼可以為我們製作網頁時候有幫助,以後將介紹查看源代碼更多方法及怎麼運用到別人的源代碼文件。
Ⅸ 按鍵精靈的源代碼裡面可以打上備注嗎這樣方便修改。
跟其它編程語言是一樣的,都有注釋的符號用來備注內容,方便以後修改辨識解讀.
// 注釋一行內容.這符號後全部為注釋內容.可以單獨一行作為備注內容,也可以在代碼的最後使用.比如:
for 3//循環3次
'和//的作用一樣.
/*......*/這兩個符號之間的全部為注釋內容.主要用來注釋段落.
以上都符號是英文輸入