学习源码
快来分享你的内容吧~
- 2023-07-18·管理员 | 首席助手 @面试鸭大家好,我是yes。 从业多年,我研读过非常多开源项目的源码,比如:RocketMQ、Kafka、Dubbo 、Netty、Tomcat、Redis、Linux 、Spring 等等。 期间也有过迷茫,也走过很多弯路,今天就来分享一些心得体会,希望对大家有点帮助。 疯狂劝退 在实习时期,我非常羡慕组里架构师,他总能丝滑地帮助同事处理各种各样的问题,甚至于别的项目组的同事都经常来请教他。 我非常狗腿查看全文Yes:我的消息队列专栏的起始篇地址:https://articles.zsxq.com/id_215wesherlix.html61311分享
github 源码阅读神器 deepwiki,自动生成源码架构图和知识库
大家好,我是 luckysnail ,今天看到了一个我之前想要做的 AI 产品,就是基于 github 仓库生成对应的教程文档。给大家看一下我之前记录的: <img src="https://pic.code-nav.cn/post_picture/1619930914211520514/9qhJ2HyoXIeEYslh.webp" alt="" width="100%" /> 没想到已经有知名的公司 https://devin.ai/ 做了,就是创造首个 AI 软件工程师 Devin 的公司。它目前完全免费,无需注册即可使用。下面来看看 DeepWiki 的能力 ## 什么是 DeepWiki? DeepWiki是由 Cognition Labs 推出的创新项目,旨在为全球每一个 GitHub 代码仓库提供**实时交互式文档**。简单来说,它是一个由 AI 驱动的 GitHub 版本“百科全书”,能够让你与对应仓库的专家进行对话,帮助你快速了解上手项目。 这个工具的特点是: - **完全免费**:对开源项目无需注册即可使用 - **即时访问**:只需修改GitHub链接就能跳转到对应Wiki - **智能问答**:可以直接向代码库“提问”并获得专业解答 - **深度分析**:能揭示代码库的隐藏结构和开发模式 ## 基础能力 使用它非常简单,你可以直接去 : https://deepwiki.com/ ,然后搜索你想要了解的项目。你也可以在 github 访问一个项目的时候把 github 改为 deepwiki 。然后你会看到: <img src="https://pic.code-nav.cn/post_picture/1619930914211520514/dW3sXNsx0sJfnH0I.webp" alt="" width="100%" />  <img src="https://pic.code-nav.cn/post_picture/1619930914211520514/f9aEchqu0suwbo8h.webp" alt="" width="100%" />  可以看到它会生成项目的文档和架构图,而且下面还可以跟文档进行对话,对话过程中它会实时的检索项目的源码进行回复:  我这里通过我之前学习的 umi-request 源码进行查看,它输出的内容和支持都是非常高,没有错误!感觉以后我学习新的 github 仓库会先到这里来学习,然后再拉取代码进行阅读。 如果你是私有仓库,你需要先登录 Devin 账户,然后进行仓库的知识库生成 ## 结语 deepwiki 目前已索引约3万个GitHub仓库,处理超过40亿行代码 。无论是对于新手小白想要学习 github 仓库,还是对应经验丰富的软件工程师都是有很大的帮助,大大节省了我们理解项目的成本
VUEX 3.x源码分析——1. 理解state
这是对vuex3.x版本的源码分析。 本次分析会按以下方法进行: * 1. **按官网的使用文档顺序**,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升,甚至面试时的回答都非常有帮助。 * 2. 在围绕某个功能展开讲解时,**所有不相干的内容都会暂时去掉**,等后续涉及到对应的功能时再加上。这样最大的好处就是能循序渐进地学习,同时也不会被不相干的内容影响。省略的内容都会在代码中以...表示。 * 3. 每段代码的开头都会说明它所在的文件目录,方便定位和查阅。如果一个函数内容有多个函数引用,这些都会放在同一个代码块中进行分析,不同路径的内容会在其头部加上所在的文件目录。 本章只讲解vuex中的state,这也是vuex官网中“核心概念”的第一个。 ### State的初始化 当我们创建了一个store时,state发生了什么。 假设我们按官网的内容,初始化了一个最基础的store: `import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const store = new Vuex.Store({ state: { count: 0 } // 这里去掉了 mutations 的内容,只关注state })` * 这里实例上是创建了一个Store类,并将count内容作为参数传入 `// ./index.js import { Store } from './store' // Vuex.Store 就是这里的Store,它是从 ./store 导入的 export default { Store, ... } // ./store.js import ModuleCollection from './module/module-collection' export class Store{ constructor(options = {}) { ... this._modules = New ModuleCollection(options) const state = this._modules.root.state // 可以看出是来自于ModuleCollection类的,那接着看下一段 ModuleCollection 的代码 } } // ./module/module-collection.js import Module from './module' export default class ModuleCollection { constructor(rawRootModule) { this.register([], rawRootModule, false) } ... register(path, rawModule, runtime=true) { const newModule = new Module(rawModule, runtime) if (path.length === 0) { this.root = newModule } else {...} ... } } // ./module/module.js export default class Module { constructor(rawModule, runtime) { this._rawModule = rawModule // 最后在这里处理我们传入的state,Module.state const rawState = rawModule.state // 这里能让我们定义的state除了可以是对象,还可以是函数 this.state = (typeof rawState === 'function' ? rawState() : rawState) || {} } }` * 至此,可以看出store在初始化时,将定义的state内容赋值到了module.state上, * 同时将module绑定到 ModuleCollection.root, * 并将实例化的ModuleCollection赋值给Store.\_modules * 最后,为了让vue能监测到state的变化并更新响应的组件,调用了vue中的reactive函数,创建了一个**响应式对象** store.\_state = {data: state},代码如下: `// ./store.js import { resetStoreState } from './store-util' export class Store { constructor(options = {}) { ... this._modules = new ModuleCollection(options) ... const state = this._modules.root.state ... // 在这里生成响应式对象 this._state resetStoreState(this, state) } } // ./store-util.js import { reactive } from 'vue' export function resetStoreState(store, state, hot) { ... store._state = reactive({data: state}) ... }` * 在这里还没有结束,因为如果按上面的内容,我们要使用定义的count属性的话,代码应该是 store.\_state.data.count * 但实际上是 store.state.count。vuex还做了一些简单的处理,就是**定义了一个getter方法**, * 另外还**对state设置了setter方法**,来阻止用户直接修改state的值 * 下面是store.js文件中目前我们所接触的所有代码 ```// ./store.js import ModuleCollection from './module/module-collection' import { resetStoreState } from './store-util' export class Store { constructor(options = {}) { ... this._modules = new ModuleCollection(options) const state = this._modules.root.state resetStoreState(this, state) } get state() { return this._state.data } set state(v) { if (__DEV__) { assert(false, `use store.replaceState() to explicit replace store state.`) } } }``` * 之前使用vuex时,经常有人说直接修改state的属性会告警且不能修改,原因就在这里。 * 但我们通过源码分析可以发现,vuex其实并没有阻止使用者修改state,只是阻止了开发环境的修改,如果部署到生成环境就可以直接修改且不会告警。至于官方建议使用mutation方式来提交修改,说更明确地追踪到状态的变化,等后续源码分析完后就能深刻地理解了。 ### mapState 辅助函数 * 先看下官方的示例: `````// 在单独构建的版本中辅助函数为 Vuex.mapState import { mapState } from 'vuex' export default { // ... computed: mapState({ // 箭头函数可使代码更简练 count: state => state.count, // 传字符串参数 'count' 等同于 `state => state.count` countAlias: 'count', // 为了能够使用 `this` 获取局部状态,必须使用常规函数 countPlusLocalState (state) { return state.count + this.localCount } }) } // 当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。 computed: mapState([ // 映射 this.count 为 store.state.count 'count' ])````` * 让我们看下vuex的源码,是如何实现辅助生成计算属性这个功能的,同时还保证了多种定义计算属性的方法,同时既可以传入对象,又可以传入数组。 * 首先 mapState 函数定义在 ./helpers.js 中 * normalizeNamespace是一个高阶函数,它接受一个函数作为参数,并返回一个函数。在它的返回函数中,接受namespace和map作为参数,返回normalizeNamespace接受的函数的执行结果。 * 在normalizeNamespace的代码中,如果namespace不是字符串类型的话,就将两个参数的值互换,这样保证我们传入的第一个参数(实例中的对象或数组)会被赋值给第二个参数。但是我感觉这里一股别扭感,按道理map在前,namespace在后更合理一些,也不用交换两个参数的值了。可能是为了保持某种使用习惯的统一吧。 * 所以 mapState 就是 normalizeNamespace 返回的函数。当按上面官方的例子执行类似 mapState(...)的时候,就是将...作为参数传入normalizeNamespace接受的函数,并执行这个函数,返回结果。 * vuex还有其他地方都有使用这种类似的高阶函数,大致就是接受的参数是一个函数A,同时返回的也是一个函数B,在返回的函数B里执行接受的函数A的操作。 * 之后 normalizeMap 函数将我们传入的state做了格式化处理,这里的代码解释了为什么mapState传入的参数既可以是对象,也可以是数组。 * 格式化参数后states后,遍历新的数组,得到res对象。这个对象的key是我们传参时的对象的key或者数组的每个元素,value是一个函数,或者我们定义的函数。 `// ./helper.js export const mapState = normalizeNamespace((namespace, states) => { const res = {} normalizeMap(states).forEach(({ key, val }) => { res[key] = function mappedState() { let state = this.$store.state let getters = this.$store.getters ... // 这里保证我们在使用mapState时,既可以直接使用state的某个属性, // 也可以函数式地定义来获得某个计算属性 // 同时在使用函数式定义时, // 还能获得当前组件的相关内容(this),以及store中的state和getters return typeof val === 'function' ? val.call(this, state, getters) : state[val] } }) return res }) // normalizeNamespace()括号包裹住的整个部分就是normalizeNamespace它接受的参数,也是一个函数,mapState就是它返回的函数 function normalizeNamespace(fn) { // 下面就是 mapState 会执行的函数内容 return (namespace, map) => { if (typeof namespace !== 'string') { map = namespace namespace = '' } else {...} return fn(namespace, map) } } function normalizeMap(map) { if (!isValidMap(map)) { return [] } return Array.isArray(map) ? map.map(key => ({key, val: key}) : Object.keys(map).map(key => ({key, val: map[key]})) }` * 所以,**mapState是一个高阶函数的返回函数**。 * 执行mapState后,会返回一个对象res,如果我们传入mapState的参数是对象,那res的key就是传参对象的key;如果传入的参数是数组,那这个res的key就是数组中的每个元素。 * **res的value都是一个待执行的mappedState函数**。 * 这个res对象作为当前组件的计算属性。 * 当计算属性所依赖的响应式数据发生变化时,对应的mappedState函数才会执行,然后根据最新的state和getter来计算并返回相应的值。 * 这种设计模式使得状态的映射可以延迟到组件的计算属性被访问时执行。这样做的好处是可以确保状态映射总是基于最新的 state 和 getters,并且可以利用 Vue 的响应式系统来自动更新。 * 同时通过分析源码,还发现了除了可以利用state以外,还能访问到getters的内容,这个是官网没写的,例如,我们可以这样修改官方的例子: ```import { mapState } from 'vuex' export default { // ... computed: mapState({ // 为了能够使用 `this` 获取局部状态,必须使用常规函数 countPlusLocalState (state) { return state.count + this.localCount } // 函数还可以接受getters,例如: countPlusLocalState_2(state, getters) { return state.count + this.localCount + getters.doubleCount } }) }``` * 最后就是官网的“对象展开运算符” * 只要理解了mapState是返回一个对象,以及展开运算符的意义,就知道必然能这样使用了 `computed: { localComputed () { /* ... */ }, // 使用对象展开运算符将此对象混入到外部对象中 ...mapState({ // ... }) }` 以上就是官网上state的相关源码,下一遍会分析getters的实现源码。
如何成为一个源码高手
<html> <head></head> <body> <div class="content ql-editor"> <p>大家好,我是yes。</p> <p><br></p> <p>从业多年,我研读过非常多开源项目的源码,比如:RocketMQ、Kafka、Dubbo 、Netty、Tomcat、Redis、Linux 、Spring 等等。</p> <p><br></p> <p>期间也有过迷茫,也走过很多弯路,今天就来分享一些心得体会,希望对大家有点帮助。</p> <p><br></p> <h1 class="ql-align-center"><strong style="background-color: rgb(60, 112, 198); color: white; font-size: 18px;">疯狂劝退</strong></h1> <p><br></p> <p>在实习时期,我非常羡慕组里架构师,他总能丝滑地帮助同事处理各种各样的问题,甚至于别的项目组的同事都经常来请教他。</p> <p><br></p> <p>我非常狗腿的黏在他身边,希望他传授一点“功力”给我,每天问东问西的,直到有天他跟我说:”其实这些东西不难,只要你多看看源码,知晓里面的原理,从报错就能直接看出问题所在。“</p> <p><br></p> <p>说出来不怕人笑话,我还去上网查了下。</p> <p><br></p> <p>查啥?查叫源码。</p> <p><br></p> <p>当天我就去下了 Spring 的源码,在打开源码那一刻,我血脉喷张,感觉自己立马就要成为人人敬仰的大牛。</p> <p><br></p> <p>背景音乐已经响起,却又戛然而止,妈的,idea 全飘红,项目如何启动我都不清楚,直接一脸懵逼。</p> <p><br></p> <p>花了一周的时间,idea 终于不红了,启动入口也找到了,背景音乐又缓缓响起,但是没过五分钟又焉了.....</p> <p><br></p> <p>我一阵点点点,从这个类跳到那个类,类中的方法又各种嵌套,每个类的名字我都念得出来,却是不知道它的作用,就这样迷失在 Spring 的类中。</p> <p><br></p> <p>不知所云,手足无措。</p> <p><br></p> <p>挣扎许久,终究化作一声叹息。</p> <p><br></p> <p>自我安慰道:算了,大牛让别人当吧,我当请教大牛的那个人,好好做绿叶该做的事情。</p> <p><br></p> <h1 class="ql-align-center"><strong style="background-color: rgb(60, 112, 198); color: white; font-size: 18px;">卷土重来</strong></h1> <p><br></p> <p>就这样浑浑噩噩的过了两年,从一个菜鸟,变成了一个——老了两岁的菜鸟。</p> <p><br></p> <p>摆脱了实习生的身份,但实际能力可能也就比实习生 CRUD 写的快一些。</p> <p><br></p> <p>没办法,虽然期间我也看了很多博客,学习的热情是高涨的,但是对知识的理解基本上是浮于表面的。</p> <p><br></p> <p>平时开会啥都能搭上话,但一要我分析让我说个所以然,就慌了。</p> <p><br></p> <p>心里没底,因为很模糊,很多时候说的头头是道,让我写P都写不出来或者说不敢写,心里对源码是惧怕的,总觉得自己的代码这么low,配不上非 CURD 的功能。</p> <p><br></p> <p>直到有天,同事向我请教消息队列的实现原理,由于天天在他前面装B,我拉不下脸说不会,只能被动的去学习去解答他的问题。</p> <p><br></p> <p>你别说,这效果杠杠的,果然人都是逼出来的。</p> <p><br></p> <p>不过这也是因为我打开了正确学习源码方式。</p> <p><br></p> <h1 class="ql-align-center"><strong style="background-color: rgb(60, 112, 198); color: white; font-size: 18px;">系统学习源码的方式</strong></h1> <p><br></p> <p>这内容我在 20 年分享过,这里再分享给星球的同学。</p> <p><br></p> <p>首先,我把 RocketMQ 官网、wiki 都读了一遍,了解具体涉及到的概念、名称、特性、架构。</p> <p><br></p> <p>对整体项目的主要角色以及之间的数据交互都了然于心,在脑海中形成了角色分布图和数据流转图。</p> <p><br></p> <p>当然也包括 RocketMQ 的功能使用,比如顺序消息发送的 api 要如何操作、事务消息怎么发的、如果消费消息等等。</p> <p><br></p> <p>在源码中提供了很多 demo,利用 demo 就可以非常快速地熟悉 api 的使用。</p> <p><br></p> <p></p> <p><br></p> <p>到这其实距离成功就差一半了,因为你已经打好了基础,且找到了突破口。</p> <p><br></p> <p>基础就是对这个项目的基本了解和功能熟悉,突破口就是这些 demo。</p> <p><br></p> <p>有了 demo 就有了 debug 的入口,你只要打好断点,debug 启动,一步一步跟下去。</p> <p><br></p> <p>这跟之前直接下源码看源码有区别吗?</p> <p><br></p> <p>当然有区别!</p> <p><br></p> <p>此时的你不像一只无头苍蝇到处乱啃,你是在知晓大体会涉及到的角色和数据流转之后读源码,这很不一样!</p> <p><br></p> <p>你会对一些方法调用有一种“认可感”,因为你知晓大致的流程,所以觉得本该如此,看完会有种,原来是这样实现的了然感。</p> <p><br></p> <p>读源码有时候会觉得代码很多,分支好多。</p> <p><br></p> <p>没事,代码在你本地,随意你修改,你可以先拷贝一份,然后把一些异常处理分支先删了。</p> <p><br></p> <p><strong style="color: rgb(60, 112, 198);">核心流程先理清楚!</strong></p> <p><br></p> <p>理清楚了一个流程之后开始画图,流程图、脑图都上。</p> <p><br></p> <p>然后反过来再把之前删减的异常代码补上,把异常部分也弄清楚,补充完整流程图、脑图等。</p> <p><br></p> <p>看看我之前分析 Kafka 的时候画的图,就类似这样的搞清楚一个流程:</p> <p><br></p> <p></p> <p><br></p> <p>然后这一模块就收工了!搞定!</p> <p><br></p> <p>接着各种其他流程模块发散出去,到最后大致的流程就都清晰了,源码也就读的差不多了。</p> <p><br></p> <p>读源码的时候肯定也会遇到一些不能理解的,先略过,主流程先搞懂。</p> <p><br></p> <p>搞懂整体核心流程之后可以抠一些细节了,比如我之前看 Dubbo 的时候就抠了一个细节:</p> <p><br></p> <p></p> <p><br></p> <p>就这个 if switch 能关系到底层 CPU 的分支预测!</p> <p><br></p> <p>再比如之前看 Kafka 的索引设计涉及到二分查找,但是源码中是改版的二分查找。把索引项分为热区和冷区,深究下去是为了避免缺页中断。</p> <p><br></p> <p></p> <p><br></p> <p>再比如 RocketMQ 里面看预热文件的时候涉及的这个方法。</p> <p><br></p> <p></p> <p><br></p> <p>这时候又涉及到 mlock、madvise 。</p> <p><br></p> <p></p> <p><br></p> <p>这些就是细节,而细节往往就是我们需要学习的地方,<strong style="color: rgb(60, 112, 198);">所以在理清整体流程之后不要错过细节</strong>。</p> <p><br></p> <p>往往你觉得很奇怪的地方可能就是一些“骚操作”,学的就是“骚操作”。</p> <p><br></p> <p>这是系统读源码的方法,是为了让自己得到晋升、学习优秀开源框架是如何设计而读的源码。</p> <p><br></p> <p>只要你攻克了一个,后续就能越读越多,越读越轻松。</p> <p><br></p> <h1 class="ql-align-center"><strong style="background-color: rgb(60, 112, 198); color: white; font-size: 18px;">为了找问题读源码的方式</strong></h1> <p><br></p> <p>有了系统读源码的基础后,你就能尝试上手去解决各种问题了。</p> <p><br></p> <p>比如,有时候是项目出错,一般而言有日志,所以通过日志搜源码就行。</p> <p><br></p> <p>如果你本身对这个框架很熟悉那当然最好,如果不熟悉通过日志搜索结合上下文其实也能找到一些缘由。</p> <p><br></p> <p>不过有时候还是得整个链路分析下来才能排查问题,这个看功力了。</p> <p><br></p> <p>有时候是因为看到一些文章的说法冲突了,一篇说 A 另一篇说 B ,如果你找不到权威的信息你只能自己去看源码,通过关键字搜。</p> <p><br></p> <p>比如我这篇写的:</p> <p><br></p> <p></p> <p><br></p> <p>这就是源码之下无秘密。</p> <p><br></p> <p>这种目的性很强的读源码就得结合当时上下文和靠个人功力了,这也是想成为大牛必须要掌握的一个能力。</p> <p><br></p> <h1 class="ql-align-center"><strong style="background-color: rgb(60, 112, 198); color: white; font-size: 18px;">最后</strong></h1> <p><br></p> <p>不知不觉说了这么多。</p> <p><br></p> <p>我只能告诉你不要被源码劝退,要<strong style="color: rgb(60, 112, 198);">自顶向下的看源码,不要一来就钻进细节,先从官网等渠道对开源框架有个全面的了解,然后看源码理清主流程</strong>。</p> <p><br></p> <p>再配合流程图、时序图、脑图等记录、归类。</p> <p><br></p> <p>然后再看细节,学学优秀开源框架的“骚操作”。</p> <p><br></p> <p>模仿它,学会它,从中能延展出很多额外的底层知识,比如上面提到的预防缺页中断,预热锁页,分支预测等等。</p> <p><br></p> <p>当然也可以先去网上搜一搜别人的源码分析文章,然后自己再上手,这样会比较舒服和顺利。</p> <p><br></p> <p>或者看一些相关的付费专栏,人家汇总整理知识,你花点小钱,节省你自己研究的时间,不亏。</p> <p><br></p> <p>还有,源码是块硬骨头这毋庸置疑,我只能告诉你看源码的准备工作和一些小心得。</p> <p><br></p> <p>最后,要抱着目的去学源码,比如你是想更进一步地去学习其中的原理,或想参考其实现解决工作上遇到的一些问题等等。</p> <p><br></p> <p>而不是为了学习源码而学习源码。</p> <p><br></p> <p>道阻且长,行则将至。</p> <p><br></p> <p>冲!</p> <p><br></p> <p>最后的最后,推荐下我在知识星球写的<strong style="color: rgb(60, 112, 198);"><a href="https://articles.zsxq.com/id_215wesherlix.html" target="_blank">消息队列-RocketMQ专栏</a></strong>,各位小伙伴可以看看,如果有啥问题或者疑问欢迎提出!</p> <p><br></p> <p>这个专栏的定位是原理入门,系统学习下来对付一般的面试消息队列原理问题应该是ok的。</p> <p><br></p> <p>我是yes,从一点点到亿点点,我们下篇见~</p> <p><br></p> <p><br></p> <p><br></p> </div> </body> </html>
