GitHub 漫游指南以测试、CI、代码质量与模块化为核心构建高质量 GitHub 项目【免费下载链接】githubGitHub 漫游指南- a Chinese ebook on how to build a good project on Github. Explore the users behavior. Find some thing interest.项目地址: https://gitcode.com/phodal/github本篇技术指南围绕《GitHub 漫游指南》中的核心章节展开系统讲解如何在一个 GitHub 开源项目上落地工程化实践从自动化测试、持续集成CI、代码质量Code Climate / JSLint到前端模块分离Require.js AMD与针对坏味道的代码重构。文中以作者实际开源的 Lettuce轻量 Ajax/SPA 库、SkillTree技能树以及一个 Node.js SQLite 的物联网服务端为真实案例读者读完可以掌握一套可直接复制的单人/小团队 GitHub 项目质量保障方案。一、如何用好 GitHub一个项目应有的组成部分如何用好 GitHub并实践一些敏捷软件开发是一个很有意思的事情。我们可以在上面做很多事情从测试到 CI再到自动部署。在 GitHub 上建设一个项目本质上是在建设一套可持续演进的工程体系。抛开敏捷这个被过度使用的词一个项目实际需要关心四件事看板式管理应用程序如 Trello简单地说就是管理软件功能CI持续集成测试覆盖率代码质量code smell。需要说明的是对于一个非远程团队例如只有一个人的项目而言Trello、Jenkins、Jira 这些重流程工具并非必需——当只有一个人的时候你只需要明确知道自己想要什么就够了。此时真正值得投入的是CI 与测试它们直接决定代码质量的底线。测试一份能说明用法的测试当拿到一个没有文档的开源库时你会先看什么源代码还是测试测试往往是最可靠的使用说明书。下面这段来自 Lettuce 项目的 Jasmine 测试清楚地示范了get方法的行为——发送一个 Ajax GET 请求、返回响应文本it(specifying response when you need it, function (done) { var doneFn jasmine.createSpy(success); lettuce.get(/some/cool/url, function (result) { expect(result).toEqual(awesome response); done(); }); expect(jasmine.Ajax.requests.mostRecent().url).toBe(/some/cool/url); expect(doneFn).not.toHaveBeenCalled(); jasmine.Ajax.requests.mostRecent().respondWith({ status: 200, contentType: text/plain, responseText: awesome response }); });其中用jasmine.Ajax拦截并模拟了真实请求通过respondWith注入响应数据。作者直言虽然写得有点扯但这段用例确实把调用方式、请求 URL、异步回调、响应解析都交代得明明白白。那么为什么要写测试作者给出了三个非常真实的理由不希望每次做完新功能再手动地一个个测试功能自动化测试不希望重构时破坏了原有功能却一无所知不敢 push 代码因为没有把握。当然作者并非 TDD 的死忠测试的目的是保证功能正常TDD 不一定能让我们写出质量更高的代码但有时 TDD 确实不错可以逼出逻辑更简单的代码。在补完一组测试之后你会得到一份像这样的测试运行输出每一个✓都是一条可执行的行为描述Ajax ✓ specifying response when you need it ✓ specifying html when you need it ✓ should be post to some where Class ✓ respects instanceof ✓ inherits methods (also super) ✓ extend methods Effect ✓ should be able fadein elements ✓ should be able fadeout elements单个测试看起来都很小但积少成多之后就可以统计出测试覆盖率。Lettuce 的lettuce.js覆盖率数据如下FileStatementsBranchesFunctionsLineslettuce.js98.58% (209 / 212)82.98% (78 / 94)100.00% (54 / 54)98.58% (209 / 212)函数覆盖率 100%、语句覆盖率 98.58%这意味着几乎每条代码路径都被执行验证过。本地测试全部通过之后下一步就是把它挂到 CI 上持续跑。CI把测试变成持续集成的门禁CI持续集成的意义在于当你 push 代码时服务器自动拉取代码并运行全部测试。这对在不同城市协作开发的场景尤其重要——只要新添加的功能有测试覆盖项目代码就会持续保持强壮。下面是一个基于 Node.js 的简单.travis.yml示例Travis CI 配置language: node_js node_js: - 0.10 notifications: email: false before_install: npm install -g grunt-cli install: npm install after_success: CODECLIMATE_REPO_TOKEN321480822fc37deb0de70a11931b4cb6a2a3cc411680e8f4569936ac8ffbb0ab codeclimate coverage/lcov.info各字段的作用分别是language/node_js声明构建环境为 Node.js并指定要测试的版本0.10可扩展为数组以做多版本矩阵测试notifications.email: false关闭邮件通知避免噪音before_install安装全局依赖这里是grunt-cliinstall执行npm install安装项目依赖after_success测试通过后把coverage/lcov.info覆盖率报告上传给 Code Climate由后者解析并展示覆盖率与代码质量。将这些徽章badge集成到README.md之后项目主页就会展示测试通过、覆盖率 XX%、质量评分 XX的状态这既是项目健康的可视证明也是吸引贡献者与使用者信任的第一印象。代码质量语法正确不等于没有坏味道像 JSLint 这类的工具只能保证代码在语法上正确却不能保证你没有写出一堆 bad smell 的代码例如重复代码过长的函数等等。Code Climate是当时一个与 GitHub 集成的工具它不仅能展示测试覆盖率还能评估代码质量。拿 Lettuce 中的 Ajax 类来看Lettuce.get function (url, callback) { Lettuce.send(url, GET, callback); }; Lettuce.send function (url, method, callback, data) { data data || null; var request new XMLHttpRequest(); if (callback instanceof Function) { request.onreadystatechange function () { if (request.readyState 4 (request.status 200 || request.status 0)) { callback(request.responseText); } }; } request.open(method, url, true); if (data instanceof Object) { data JSON.stringify(data); request.setRequestHeader(Content-Type, application/json); } request.setRequestHeader(X-Requested-With, XMLHttpRequest); request.send(data); };Code Climate 会针对它报出一系列问题Missing use strict statement. (Line 2)Missing use strict statement. (Line 14)Lettuce is not defined. (Line 5)这些还算小问题更值得警惕的是这类提示Similar code found in two :expression_statement nodes (mass 86)mass 86意味着存在高相似度的重复代码段——这正是重构的信号看到重复就应该考虑抽取公共方法。二、模块分离与测试在改造开源项目时作者遵循的完整路径是将 fork 的代码读懂、重构、升级版本、调整、添加新功能、添加测试、添加 CI、添加分享奋斗近半个月后才 almost finish。以 Lettuce 为例一个基本合格的项目应该包含代码质量Code ClimateCI 状态Travis CI测试覆盖率96%自动化测试npm test文档如果再按 Web Developer 路线图补齐还应有版本管理与自动部署。本节的焦点是其中最重要的两个环节代码模块化与自动化测试。代码模块化用 Require.js 管理前端依赖以 SkillTree 项目为例其源码大致分为三部分namespace函数负责命名空间Calculator即TalentTree负责解析、生成 URL、头像、依赖等核心逻辑Skill负责 tips技能提示部分。这些代码原本都挤在一个 JS 文件里。对一个库来说这或许是好事发布简单但对一个项目来说并非如此——可维护性很差。SkillTree 依赖 jQuery 与 Knockout好在 Knockout 可以用 Require.js 进行管理于是引入 Require.jsscript typetext/javascript>require.config({ baseUrl: app, paths:{ jquery: lib/jquery, json: lib/json, text: lib/text } }); require([scripts/ko-bindings]); require([lib/knockout, scripts/TalentTree, json!data/web.json], function(ko, TalentTree, TalentData) { use strict; var vm new TalentTree(TalentData); ko.applyBindings(vm); });baseUrl: app所有模块的相对根目录paths为jquery、json、text等库配置短路径别名text、json插件主要用于处理web.json——技能数据以 JSON 描述加载后注入TalentTree构造器再通过 Knockout 的applyBindings绑定到视图模型。经过模块化拆分后不同职责的类进入了不同的 JS 文件. |____Book.js |____Doc.js |____ko-bindings.js |____Link.js |____main.js |____Skill.js |____TalentTree.js |____Utils.js其中Book和Link都继承自Doc。这是标准的 AMD 风格 Class 写法define([scripts/Doc], function(Doc) { use strict; function Book(_e) { Doc.apply(this, arguments); } Book.prototype new Doc(); return Book; });而Doc类可以看作是 SkillTree 中所有条目类的缩影define([], function() { use strict; var Doc function (_e) { var e _e || {}; var self this; self.label e.label || (e.url || Learn more); self.url e.url || javascript:void(0); }; return Doc; });这段代码有两点值得注意self this技巧考虑到 JavaScript 中this的隐性绑定问题在回调或事件中this可能丢失作者用self this保存引用这是 AMD 时代的经典防御写法返回对象 vs 返回函数Doc等类返回的是对象因此调用方需要new一个实例而Utils类返回的是函数当然函数在 JavaScript 中也是一个对象return { getSkillsByHash: getSkillsByHash, getSkillById: getSkillById, prettyJoin: prettyJoin };模块分离的核心收益是每个文件职责单一、可独立测试、可被 Require.js 按需加载这也为后续的自动化测试提供了基础。自动化测试push 即跑测试的 Travis 流程作者习惯使用 Travis CI其.travis.yml配置如下language: node_js node_js: - 0.10 notifications: email: false branches: only: - gh-pages使用gh-pages分支的原因是一 push 代码就可以自动测试、自动部署——静态站点直接发布到 GitHub Pages好处一堆堆。接着需要在package.json里添加测试脚本scripts: { test: mocha }这样 push 代码时便会自动跑所有测试。由于 Mocha 的主要配置放在mocha.opts还需要这样配置--reporter spec --ui bdd --growl --colors test/spec各参数的含义--reporter spec使用 spec 报告器输出逐条行为描述--ui bdd使用 BDD 风格describe/it--growl测试结束后触发系统通知--colors彩色输出test/spec最后一行指定测试文件的目录。JSLint以更严格的编码约定约束代码JSLint 定义了一组比 ECMA 语言规范更严格的编码约定。它秉承一条古老的编程原则——能做并不意味着应该做JSLint 会对自己认为有问题的编码实践加标志同时指出明显的错误从而促使你养成好的 JavaScript 编码习惯。当 JS 写得不合理时测试就无法通过。例如line 5 col 25 A constructor name should start with an uppercase letter. line 21 col 62 Strings must use singlequote.这是一种驱动写出更规范 JavaScript 的方法——把代码规范纳入 CI 门禁让风格问题在合并前就暴露。Mocha支持 TDD/BDD 的 JS 测试框架Mocha 是一个优秀的 JS 测试框架支持 TDD/BDD 两种风格结合 should.js / expect / chai / better-assert 等断言库可以轻松构建各种风格的测试用例。使用 Mocha should 断言跑出来的效果如下Book,Link Book Test ✓ should return book label url Link Test ✓ should return link label url测试示例在 Node 端测试 AMD 模块由于 SkillTree 用 Require.js 管理浏览器端模块在 Node 后台写测试时也需要用 Require.js 来管理依赖——这正是下面这个测试看起来很长的原因。看一下Book的测试/* global describe, it */ var requirejs require(requirejs); var assert require(assert); var should require(should); requirejs.config({ baseUrl: app/, nodeRequire: require }); describe(Book,Link, function () { var Book, Link; before(function (done) { requirejs([scripts/Book], function (Book_Class) { Book Book_Class; done(); }); }); describe(Book Test, function () { it(should return book label url, function () { var book_name Head First HTML与CSS; var url http://www.phodal.com; var books { label: book_name, url: url }; var _book new Book(books); _book.label.should.equal(book_name); _book.url.should.equal(url); }); }); });关键技术点requirejs.config({ baseUrl: app/, nodeRequire: require })让 Require.js 在 Node 环境下以app/为根目录解析模块nodeRequire指向 Node 的require使 AMD 模块可以加载 Node 模块用before(done)异步加载被测模块scripts/Book再在it中new Book(books)实例化并断言label、url两个字段。剥掉 AMD 加载这一层一个典型的 BDD 用例其实长这样用 Jasmine 或 Mocha 都一样describe(Book Test, function () { it(should return book label url, function () { var book_name Head First HTML与CSS; var url http://www.phodal.com; var books { label: book_name, url: url }; var _book new Book(books); _book.label.should.equal(book_name); _book.url.should.equal(url); }); });最后的断言才是测试的核心——它保证测试是有用的不仅验证能跑更验证跑得对。三、代码质量与重构当出现以下两种情形时就该重视代码质量了你写了一大堆代码却没意识到里面有大量重复你写了一大堆测试却不知道覆盖率有多少。Code Climate静态分析、评分与坏味道识别Code Climate 整合一组静态分析工具的结果到一个单一的、实时的报告让团队能够识别热点hotspots、评估新方法并改进代码质量。简单来说就是两件事给代码评分、找出代码中的坏味道。以下是一个 Node.js 物联网场景下各模块的质量评分表字段评分、文件、复杂度、重复度、变更频率 Churn、C/M 比率、覆盖率、坏味道数量RatingNameComplexityDuplicationChurnC/MCoverageSmellsAlib/coap/coap_request_handler.js24062.646.4%0Alib/coap/coap_result_helper.js14023.480.0%0Alib/coap/coap_server.js16055.244.0%0Alib/database/db_factory.js8033.892.3%0Alib/database/iot_db.js7061.058.8%0Alib/database/mongodb_helper.js630114.535.0%0Clib/database/sqlite_helper.js3286104.535.0%2Blib/rest/rest_helper.js196234.737.5%2Alib/rest/rest_server.js17028.688.9%0Alib/url_handler.js9052.294.1%0观察这张表大部分模块拿到了A评分复杂度低、无重复、无坏味道lib/database/sqlite_helper.js拿到C其Duplication 86、Smells 2——重复度和坏味道都明显超标是最需要重构的文件lib/rest/rest_helper.js拿到BDuplication 62同样是重复代码的重灾区。这正体现了 Code Climate 的价值它把哪里质量差、差在哪变成一眼可读的量化指标。代码的坏味道SQLite Helper 的去重重构按评分表的指引打开lib/database/sqlite_helper.js——它有两个坏味道Similar code found in two :expression_statement nodes (mass 86)位置在lib/database/sqlite_helper.js:58…61与lib/database/sqlite_helper.js:64…67。这两段代码几乎一样一个执行 DELETE一个执行 SELECTSQLiteHelper.prototype.deleteData function (url, callback) { use strict; var sql_command DELETE FROM config.table_name where URLHandler.getKeyFromURL(url) URLHandler.getValueFromURL(url); SQLiteHelper.prototype.basic(sql_command, callback);与SQLiteHelper.prototype.getData function (url, callback) { use strict; var sql_command SELECT * FROM config.table_name where URLHandler.getKeyFromURL(url) URLHandler.getValueFromURL(url); SQLiteHelper.prototype.basic(sql_command, callback);注意这已经是修改过的重复——原来的代码重复得更彻底。重构前postData、deleteData、getData三个方法各自都做了一遍打开数据库 → 执行 SQL → 处理错误 → 关闭数据库 → 回调的完整流程SQLiteHelper.prototype.postData function (block, callback) { use strict; var db new sqlite3.Database(config.db_name); var str this.parseData(config.keys); var string this.parseData(block); var sql_command insert or replace into config.table_name ( str ) VALUES ( string );; db.all(sql_command, function (err) { SQLiteHelper.prototype.errorHandler(err); db.close(); callback(); }); }; SQLiteHelper.prototype.deleteData function (url, callback) { use strict; var db new sqlite3.Database(config.db_name); var sql_command DELETE FROM config.table_name where URLHandler.getKeyFromURL(url) URLHandler.getValueFromURL(url); db.all(sql_command, function (err) { SQLiteHelper.prototype.errorHandler(err); db.close(); callback(); }); }; SQLiteHelper.prototype.getData function (url, callback) { use strict; var db new sqlite3.Database(config.db_name); var sql_command SELECT * FROM config.table_name where URLHandler.getKeyFromURL(url) URLHandler.getValueFromURL(url); db.all(sql_command, function (err, rows) { SQLiteHelper.prototype.errorHandler(err); db.close(); callback(JSON.stringify(rows)); }); };三个方法高度相似重复的建连接、查错误、关连接样板代码分散在各处。重构的核心思路是抽取公共方法basic把执行 SQL 错误处理 关闭连接 回调收拢到一处SQLiteHelper.prototype.basic function(sql, db_callback){ use strict; var db new sqlite3.Database(config.db_name); db.all(sql, function (err, rows) { SQLiteHelper.prototype.errorHandler(err); db.close(); db_callback(JSON.stringify(rows)); }); }; SQLiteHelper.prototype.postData function (block, callback) { use strict; var str this.parseData(config.keys); var string this.parseData(block); var sql_command insert or replace into config.table_name ( str ) VALUES ( string );; SQLiteHelper.prototype.basic(sql_command, callback); }; SQLiteHelper.prototype.deleteData function (url, callback) { use strict; var sql_command DELETE FROM config.table_name where URLHandler.getKeyFromURL(url) URLHandler.getValueFromURL(url); SQLiteHelper.prototype.basic(sql_command, callback); }; SQLiteHelper.prototype.getData function (url, callback) { use strict; var sql_command SELECT * FROM config.table_name where URLHandler.getKeyFromURL(url) URLHandler.getValueFromURL(url); SQLiteHelper.prototype.basic(sql_command, callback); };重构之后的postData/deleteData/getData都只剩拼 SQL → 调basic两步职责分明数据库连接、错误处理、关闭与回调这些横切逻辑统一由basic承担。作者在文末也坦诚了一个有趣的事实重构完后的代码比原来还长这似乎是个问题。这提醒我们代码量的增减不是衡量重构成功与否的唯一标准——重复度的下降、单一职责的建立才是当消除重复与方法过长之间需要权衡时应当结合具体场景比如是否可以再拆出URLHandler层、是否可以用配置驱动 SQL 模板做进一步收敛最关键的依然是那一步——在任何重构之前确保有足够的测试兜底否则没有测试的重构就是裸奔。结语纵观整条 GitHub 项目构建链路它其实是一条环环相扣的质量流水线写测试用 Jasmine / Mocha 描述行为让测试本身成为可读的文档跑覆盖率量化测试的充分程度语句、分支、函数、行上 CI通过.travis.ymlnode_js 环境 npm scripts mocha.opts把push 即测试变成常态并把覆盖率报告交给 Code Climate管质量用 JSLint 约束编码规范用 Code Climate 定位重复代码与坏味道模块化用 Require.js AMD 拆分前端模块Doc/Book/Link/TalentTree让每个类可独立测试、按需加载重构针对高分贝的重复如 SQLite Helper 的三个方法用抽取公共方法消除坏味道重构前后都有测试护航。这套方法既适用于单人开源项目也适用于分布式小团队协作。它不追求复杂流程只抓住两件真正提升代码质量的事让测试自动跑起来让代码坏味道看得见。有了这两点你的 GitHub 项目才称得上构建完成。【免费下载链接】githubGitHub 漫游指南- a Chinese ebook on how to build a good project on Github. Explore the users behavior. Find some thing interest.项目地址: https://gitcode.com/phodal/github创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
华为交换机路由器VLAN配置实例:从Access到VLANIF实战 简介:一份以华为路由器R2621与交换机S3026e为例的VLAN配置实战文档,面向网络工程初学者、运维人员及华为设备认证备考者,解决多VLAN组网中的地址规划、虚拟网与物理网互通及访问控制问题。文档从4台PC的实验拓扑出发,给出各终端IP… · 2026/9/23 16:40:32
博客系统开发实战:Vue3与Pinia构建轻量级个人站点 我无法根据当前输入内容生成符合要求的博文。原因如下:项目标题“屌丝网,上线了 哈哈,”属于无实质技术/功能指向的网络调侃式表达,缺乏明确领域归属(科技/生活/职场/手工等)、无具体功能描述、无技术动因或… · 2026/9/23 16:40:32
Python实现声纹识别:端到端流水线与ECAPA-TDNN实战 简介:本资源是一套基于Python实现的声纹识别算法设计源码,面向语音处理初学者、机器学习实践者及智能身份认证方向开发者,解决说话人识别这一典型生物特征识别问题,适用于安全验证、语音助手身份确认等实际场景。压缩包共122个文件… · 2026/9/23 17:26:32
TVBOX接口配置与直播源获取完全指南:从入门到自动化维护 上个月,一个朋友发来截图问我:电视上装了TVBOX,首页电影分类能正常刷出来,但点进“直播”标签,转两圈之后直接提示无数据。我让他打开配置详情截图,一眼就发现了问题——订阅地址里只有点播接口,… · 2026/9/23 17:26:32
python判断闰年踩坑实录:源码解析3个高频Bug python判断闰年踩坑实录:源码解析3个高频Bug 刚接手老项目,改个日期校验,结果一跑测试全红。屏幕上全是 AssertionError 和 ValueError ,StackTrace… · 2026/9/23 17:26:25
Flink Hive 方言 LOAD DATA 语句完全指南:语法、参数与源码实现剖析 大数据流处理批处理数据工程 【免费下载链接】flink 项目地址: https://gitcode.com/gh_mirrors/fli/flink 点击查看 免费下载 LOAD DATA 是 Flink Hive 方言中用于将用户指定目录或文件中的数据装载进 Hive 表的核心语句,本质上是纯拷贝/移动ÿ… · 2026/9/23 17:26:25
凸焊修磨器与点焊电极帽修磨器的本质区别及应用场景解析 车间里一个很有意思的现象:点焊工位的电极帽只要焊够额定点数,操作工基本都会主动拆下来修磨,或者机器人焊钳自己跑去修磨器上“报到”;可凸焊工位那对大电极,很多人是几个月都不带正眼看一下的。直到凸焊螺母扭矩不合… · 2026/9/23 17:26:25
搞定U分布高频面试题:3个核心考点避开80%的坑 搞定U分布高频面试题:3个核心考点避开80%的坑 官方文档里关于U形分布的数学推导看得人头皮发麻,公式堆砌让人根本抓不住重点。 但到了面试现场,面试官问的往往不是让你手推积分,而是考察你对 均匀分布 (Uniform… · 2026/9/23 17:26:19
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29