没有测量就没有优化。只有测量到团队的工程效率后,我们才有可能制定提升效率的行动。Google设计出了GSM框架来测量工程效率。 + +```markmap +- GSM框架 + - 目标(Goals) + - 代码质量(Quality of the code) + - 工程师注意力(Attention from engineers) + - 认知复杂度(Intellectual complexity) + - 节奏和速度(Tempo and velocity) + - 满意度(Satisfaction) + - 信号(Signals) + - 指标(Metrics) + - 定量指标(Quantitative) + - 定性指标(Qualitative) +``` + +工程效率的测量一般发生在大规模团队或组织级别,我所经历的项目上并没有此实践。对小型团队来说,可以通过简单的一些问卷调查这类定性的方式来收集团队成员的反馈,当然也可以通过一些量化的指标如流水线构建速度、迭代开发速率、代码静态分析结果、测试覆盖率等指标测量团队的工程效率。 + +推荐进一步阅读的文章: + +- [敏捷交付的工程效能治理](https://insights.thoughtworks.cn/engineering-productivity-governance-in-agile-delivery/) + +## 总结 + +软件工程是一项复杂的知识工程,这让其区别于传统的项目管理。Google的软件工程文化与以人为中心的敏捷过程所倡导的理念有很多相似之处。 + +但反观国内很多软件公司虽然也用上了敏捷过程的方法论,但底层的文化土壤还是以过程为中心的方法论,对团队成员的不信任,没有分享文化,团队领导一言堂还是存在的。希望这种现象能随着国内IT行业的逐渐成熟越来越少吧。 diff --git a/content/dev/software-engineering-at-google/process.zh-cn.md b/content/dev/software-engineering-at-google/process.zh-cn.md new file mode 100644 index 000000000..d80009140 --- /dev/null +++ b/content/dev/software-engineering-at-google/process.zh-cn.md @@ -0,0 +1,592 @@ +--- +title: "Google软件工程之过程篇" +date: 2022-08-08 +draft: false +tags: ["软件工程", "Style Guide", "Code Review", "Technical Documentation", "Unit Test", "Test Pyramid", "Deprecation"] +keywords: "" +description: "本文是《Software Engineering at Google》的读书笔记,同时会穿插分享我对软件工程的理解。本文主要介绍软件工程的过程,主要包括Code Review、技术文档与自动化测试(单元、集成、E2E)。" +isCJKLanguage: true +og_image: "https://img.bmpi.dev/d70f36dc-76bb-fa3c-4cb9-43ce186b8203.png" +categories: [ + "软件工程" +] +markmap: + enabled: true + id: "software-engineering-at-google-process" +--- + +- [风格指南(Style Guide)](#风格指南style-guide) +- [代码评审(Code Review)](#代码评审code-review) + - [Code Review v.s. Code Diff](#code-review-vs-code-diff) +- [技术文档(Technical Documentation)](#技术文档technical-documentation) +- [测试(Testing)](#测试testing) + - [单元测试(Unit Testing)](#单元测试unit-testing) + - [测试替身(Test Doubles)](#测试替身test-doubles) + - [较大型的测试(Larger Testing)](#较大型的测试larger-testing) +- [弃用(Deprecation)](#弃用deprecation) +- [总结](#总结) + +[上篇](/dev/software-engineering-at-google/culture/)介绍了Google软件工程中的文化部分,本篇介绍软件工程中主要的过程部分,包括编码风格指南、代码评审、技术文档、自动化测试(单元测试、集成测试与较大型测试)与弃用。 + +> 以下是《Software Engineering at Google》一书第三部分过程篇的思维导图,由于此部分占全书近40%,所以本文不会详细的介绍其中的概念,想详细了解的读者建议阅读原书。本文会结合此书这部分内容分享作者的个人理解及相关经验。 + +```markmap +# Google软件工程 +## 文化(Culture) +## 过程(Process) +- Style Guide +- Code Review + - 好处 + - 代码正确性 + - 代码可读性 + - 代码一致性 + - 促进团队知识共享 + - 塑造团队工程文化 + - 最佳实践(Best Practices) + - 友善且专业 + - 小的变更 + - 清晰的变更描述 + - 小规模评审 + - 尽可能自动化 + - 金字塔模型 + - 评审类型 + - 特性代码 + - 优化代码 + - Bug Fixes + - 重构 +- Technical Documentation + - 一些实践 + - 文档即代码 + - 文档流程化 + - 了解受众 + - 文档评审 + - 文档类型 + - 引用类(Reference) + - 设计类(Design) + - 教程类(Tutorial) + - 概念类(Conceptual) + - 登陆页(Landing Page) + - 文档写作最佳实践 + - 5W+1H + - 三段式 + - 好文档的特征 + - 弃用文档 +- Testing + - 测试概述 + - 测试代码的好处 + - 更少的Debugging + - 提升对代码变更的信心 + - 测试代码是更好的文档 + - 让代码评审更简单 + - 好的测试反向提升代码设计 + - 自动化测试让持续交付变的更容易 + - 设计测试套件 + - 测试大小 + - 小 + - 中 + - 大 + - 实践 + - 测试范围 + - 窄范围测试 + - 中范围测试 + - 广范围测试 + - 实践 + - 金字塔模型 + - 反模式 + - 测试模型的选择 + - The Beyoncé Rule + - 测试原则:测试所有不想被破坏的东西 + - 测什么 + - 关于测试覆盖率 + - 仅测量单元测试覆盖率 + - 覆盖率目标是底线 + - 考虑被测试的行为 + - 测试套件的陷阱 + - 脆弱的测试 + - 缓慢的测试 + - Google测试文化的历史 + - 针对新员工的专项课程 + - 测试认证计划 + - 在厕所推广测试(TotT) + - 自动化测试的局限 + - 无法取代人工探索性测试 + - 单元测试(Unit Testing) + - 优势 + - 提高可维护性 + - 脆弱的测试 + - 不清晰的测试 + - 好的实践 + - 测试行为而非方法 + - 测试名称应提现测试行为 + - 测试不应包含逻辑 + - DAMP原则 + - 测试替身(Test Doubles) + - 基本概念 + - 缝(Seams) + - Mocking框架 + - 测试替身技术 + - 伪造(Faking) + API的轻量级实现 + - 打桩(Stubbing) + 赋予函数行为的过程 + - 交互测试(Interaction Testing) + 验证函数调用行为及参数 + - 何时使用 + - 真实实现 + - 伪造(Faking) + - 打桩(Stubbing) + - 交互测试(Interaction Testing) + - 较大型的测试 + - 为什么需要 + - 挑战 + - 结构 + - 类型 + - 工作流 +- Deprecation + - 为什么 + - 代码是负债而非资产 + 因为需要维护 + - 过时的系统需要弃用 + - 维护成本太高 + - 功能无用、重复或被替代 + - 旧不意味着过时 + - 代码越少,功能越多 + - 困难 + - 系统用户越多,越难弃用 + - 系统启用,代码保留 + - 被弃用的,和没有准备好的 + - 演进而非弃用 + - 在设计时考虑弃用 + - 谨慎启动新项目 + - 类型 + - 咨询 + - 强制 + - 警告 + - hope is not a strategy + - alert fatigue + - 流程 + - Owner + - 里程碑 + - 工具 + - How/By who + - Code Search + - Testing +## 工具(Tool) +``` + +## 风格指南(Style Guide) + +```text +We value “simple to read" over "simple to write." (Software Engineering at Google - Style Guides and Rules) +``` + +代码可能只会被写一次,但会被读很多次。如果团队成员的代码风格都不统一,可读性会很差,所以保持团队代码风格统一很重要。 + +历史证明,能写的很飘逸的编程语言使用人数一般都不会很多,典型的如古老的Perl语言,可以达到“一人千面”的代码风格。而写起来中规中矩甚至没有啥高级技巧的语言如Java、Go等在工业上反而用的很多。 + +Google的代码风格指南不太适合一般规模的公司,所以此部分不做过多介绍。从我的个人经验来说,一般项目上会配置一套自动化的代码风格检查工具(如[checkstyle](https://github.com/checkstyle/checkstyle)),甚至会集成到流水线(Pipeline)中强制团队保持一致的代码风格。某些编程语言如Go在构建工具中也提供了gofmt的代码格式化工具。 + +代码风格指南只能解决一些很基本的可读性问题,如代码缩进、函数命名风格、代码行数限制等。但代码的可读性可不只体现在这些表面,更深层次的可读性问题如API语义的可读性该怎么解决?一个可行的实践是代码评审。 + +## 代码评审(Code Review) + +代码评审是如此重要,以至于其在Google是必须做的一个实践过程。它能提供以下的好处: + +- 代码正确性:评审人员可能发现评审代码中的逻辑问题,从而提前消除一些潜在的Bug; +- 代码可读性:代码能否被其他人很容易的理解?API语义设计是否合理?是否包含测试?是否有必要的文档与注释? +- 代码一致性:代码风格是否与团队和组织保持一致? +- 促进团队知识共享:代码评审可以让团队其他成员了解你所做工作的上下文; +- 塑造团队工程文化:团队保持代码评审的实践,本身也是团队工程文化的一部分,能让新的成员迅速适应团队工程文化; + +代码评审的最佳实践有以下: + +- 友善且专业 +- 小的变更 +- 清晰的变更描述 +- 小规模评审 +- 尽可能自动化 +- 金字塔模型 + +代码评审金字塔模型如下图所示: + +{{< figure src="https://img.bmpi.dev/94f914a6-e7cd-1075-062b-dfbdf3880f15.png" caption="图片来源:《The Code Review Pyramid - Gunnar Morling》" link="https://www.morling.dev/blog/the-code-review-pyramid/">}} + +代码评审的反模式是倒金字塔模型,也就是很多时间花费在了可以自动化执行的部分比如代码风格的统一、自动化测试等,但在金字塔模型里,代码评审应该把主要的精力放在API语义、实现语义及文档等部分。 + +### Code Review v.s. Code Diff + +Diff 和 Review 的区别在于前者是一个团队集体行动,团队成员一块看某个开发者前一天写的代码,这样的好处在于每个人都能反馈,也能了解其他人做的工作,防止一些信息不同步的问题。代码评审一般是一两个人(可能甚至是团队外部的人)去审查对方要合入主干分支的代码,更适合外部人员提交代码到主干这种 GitHub PR 分支管理模式。 + +我所在项目的团队每天会做 Code Diff ,这是个必须的实践。团队规模在几人以内可以让每个人都有时间讲解自己的代码,如果代码太多,那可以给每个人一个时间限制。如果团队太大那可以拆分成多个 stream 来管理,总之 Diff 的人员不能太多,但每天都应该花时间做,因为收益要高于成本,可以统一代码风格,保证可读性,提高成员技术水平。 + +## 技术文档(Technical Documentation) + +```markmap +- Technical Documentation + - 一些实践 + - 文档即代码 + - 文档流程化 + - 了解受众 + - 文档评审 + - 文档类型 + - 引用类(Reference) + - 设计类(Design) + - 教程类(Tutorial) + - 概念类(Conceptual) + - 登陆页(Landing Page) + - 文档写作最佳实践 + - 5W+1H + - 三段式 + - 好文档的特征 + - 完整 + - 准确 + - 清晰 + - 弃用文档 +``` + +技术文档与代码一样应该得到开发者同等的重视,但有太多文档与代码不同步的场景出现,导致文档的可用性大大降低。为什么会出现这种问题?一方面是因为开发者重视度不够的问题,另外一方面是因为写一份好的技术文档并不是一件简单的事情。 + +如何写一份好的技术文档?推荐阅读如下的文章: + +- [Technical Writing for Developers](https://css-tricks.com/technical-writing-for-developers/) +- [Technical Writing | Google Developers](https://developers.google.com/tech-writing/overview) +- [SEO Copywriting Guide](https://www.semrush.com/blog/seo-copywriting/) + +开发人员不喜欢文档的另外一个原因在于,代码和文档的工作流程并不相同,一般文档都存放在与代码不同的位置,比如某个FTP目录以Word的格式存在。要是文档的编写可以和代码在同一套工作流里,就能极大的降低开发者的心智负担,这正是[Docs-as-code](https://www.writethedocs.org/guide/docs-as-code/)的设计理念,具体的流程实践可以看这篇文章: + +- [Working in public — our docs-as-code approach](https://blog.cloudflare.com/our-docs-as-code-approach/) + +## 测试(Testing) + +```markmap +- 测试概述 + - 测试代码的好处 + - 更少的Debugging + - 提升对代码变更的信心 + - 测试代码是更好的文档 + - 让代码评审更简单 + - 好的测试反向提升代码设计 + - 自动化测试让持续交付变的更容易 + - 设计测试套件 + - 测试大小 + - 小 + - 单线程 + - 无法执行阻塞线程的操作 + (测试替身) + - 类比单元测试 + - 中 + - 可跨进程但不能跨机器 + - 类比集成测试 + - 大 + - 可跨机器 + - 主要验证环境配置 + - 在构建和发布时执行此类测试 + - 类比E2E测试 + - 实践 + - 测试之间应隔离 + - 清晰简单的测试 + - 测试范围 + - 窄范围测试 + - 验证代码逻辑 + - 类比单元测试 + - 中范围测试 + - 验证系统组件交互 + - 类比单元测试 + - 广范围测试 + - 验证系统间交互 + - 类比E2E测试 + - 实践 + - 金字塔模型 + - 80%单元测试 + - 15%集成测试 + - 5%E2E测试 + - 反模式 + - 冰激凌甜筒模型 + - 沙漏模型 + - 测试模型的选择 + - 工程生产力 + - 产品信心 + - The Beyoncé Rule + - 测试原则:测试所有不想被破坏的东西 + - 测什么 + - 性能 + - 行为正确性 + - 可用性 + - 安全 + - 异常与错误 + - 关于测试覆盖率 + - 仅测量单元测试覆盖率 + - 覆盖率目标是底线 + - 考虑被测试的行为 + - 对系统的正常工作有信心 + - 对依赖的破坏性更新有信心 + - 测试稳定可靠吗 + - 测试套件的陷阱 + - 脆弱的测试 + - 糟糕的测试代码 + - 滥用Mock + - 缓慢的测试 + - 处理大数据集 + - 启动系统 + - 同步等待其他依赖 + - Google测试文化的历史 + - 针对新员工的专项课程 + - 测试认证计划 + - 在厕所推广测试(TotT) + - 自动化测试的局限 + - 无法取代人工探索性测试 +``` + +测试是软件工程过程中很重要的一个组成部分,而这里的测试主要指自动化测试过程,人工测试占比很少。测试也有一个金字塔模型,如下图所示: + +![](https://img.bmpi.dev/ca9e23e9-b0c0-3b22-bce3-abbdcabc684f.png) + +关于测试金字塔的细节,推荐阅读这篇文章: + +- [The Practical Test Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html) + +开发人员写自动化测试有如下好处: + +- 更少的Debugging:有了自动化测试后,系统的很多行为可以通过测试代码观察到。当然Bug一旦产生说明测试代码覆盖不全面,需要补上相关的测试,久而久之,测试代码就形成了非常全面的防护网。 +- 提升对代码变更的信心:当有了测试防护网后,对代码一旦产生破坏性的更新,测试代码会失败,这就给开发人员机会在部署前去修复此问题。 +- 测试代码是更好的文档:当面对一个完全陌生的代码库时,除了有限的文档,另外一个了解系统行为的方式就是看测试代码。测试代码相比文档,有着更全面清晰的业务细节,能给予开发人员更多的信息去了解此业务系统。 +- 让代码评审更简单:测试代码相比生产代码更接近业务视角,能让评审人员从业务系统对外行为的视角去了解生产代码的意图。这样也能让评审人员做出更有效的反馈意见。 +- 好的测试反向提升代码设计:要让系统模块具备一定的测试性才能写出测试代码,所以有测试的代码从设计的角度看,其可读性与解耦度相比没有测试代码的要更高。敏捷实践中推崇的TDD(Test Driven Development)就是一种通过测试驱动出好的实现代码的实践。 +- 自动化测试让持续交付变的更容易:如果没有自动化测试的帮助,代码部署上去后出Bug的概率要更高,这会提高系统交付的时间。 + +> 没有测试代码的系统是遗留系统。 + +### 单元测试(Unit Testing) + +```markmap +- 单元测试(Unit Testing) + - 优势 + - 小且快,能立即获得反馈 + - 容易编写 + - 能提高测试覆盖率 + - 失败时能快速定位问题 + - 能作为技术文档 + - 提高可维护性 + - 脆弱的测试 + - 不变的测试 + - 重构 + (不应修改测试) + - 新特性 + (不应修改测试) + - Bug Fixes + (不应修改测试) + - 业务行为改变 + (只有在此情况下才修改测试) + - 测试公开接口 + - 避免测试具体实现 + - 合适的测试范围 + - 局部支持类无需独立测试 + - 只测试公开的接口 + - 通用支持类需有独立测试 + - 测试状态而非交互 + - 不清晰的测试 + - 编写完整简洁的测试 + - 通过冗余提供完整的信息 + - 不包含无关的信息 + - 好的实践 + - 测试行为而非方法 + - given + - when + - then + - 测试名称应提现测试行为 + - 测试行动 + - 结果预期 + - 测试不应包含逻辑 + - 测试代码简单直接 + - 适当冗余而非精简 + - DAMP原则 + Descriptive And Meaningful Phrases + 当在测试代码共享代码和数据时采用此规则 +``` + +单元测试作为占比测试金字塔最大部分的底座,重要性不言而喻。它的优势很多,但Google在多年的实践中发现,提高单元测试的可维护性非常重要。而难以维护的测试代码主要有两方面造成: + +- 测试脆弱:当在代码重构、添加新特性及修复Bug时,会出现一些测试无法跑通,只能通过修改测试的方式来解决,这说明已有的测试很脆弱。好的测试应该只有在系统的业务行为发生改变时,才需要修改生产代码和测试代码。造成测试脆弱的原因有很多种,可能的原因包括测试隔离没做好,比如依赖了很多共享的全局性状态,或者测试了非公开的函数或方法,又或者测试的粒度过细,把很多实现细节给测试了。 +- 测试不清晰:不清晰的原因也有很多方面,比如测试的名称并没有体现其测试意图,在单个测试中测试了一些不必要的行为,又或包含了很多无关的信息。 + +要提高可维护性,一些好的实践包括以下方面: + +- 测试行为而非方法:很多测试框架如Junit都倡导Given/When/Then三段式测试编写方式,这样可以从验收标准(Acceptance Criteria)的业务视角去编写测试,而非针对单个函数或方法去编写测试(这很容易写出脆弱的测试)。 +- 测试名称应提现测试行为:当单元测试失败时,最先看到的就是测试失败单元的名称,好的测试名称能以最直接的方式体现该测试意图,所以测试名称长一些也可以。 +- 测试不应包含逻辑:因为测试单元本身并没有额外的测试,如果测试包含了比较复杂的逻辑,可能会导致测试代码的Bug。所以测试代码中尽可能不包含逻辑计算的过程。 +- DAMP(Descriptive And Meaningful Phrases)原则:在生产代码上业界倡导[DRY](https://zh.wikipedia.org/wiki/一次且仅一次)(Don't repeat yourself)的基本原则。而在测试代码中,正如上面几条实践表明,一定程度上的代码冗余是有必要的,这能帮助我们编写出简单而清晰的测试代码。 + +单元测试的代码执行速度一定要快,但在要测试的生产代码中,可能包含了执行速度很慢的代码,比如网络或文件等I/O操作,又或者对数据库的请求,甚至需要整个应用启动来获得完整的执行环境。如何将这类慢的代码与真正要测试业务逻辑的代码隔离开来?那就是接下来要介绍的测试替身技术。 + +### 测试替身(Test Doubles) + +```markmap +- 测试替身(Test Doubles) + - 基本概念 + - 缝(Seams) + 通过使用测试替身 + 实现可测试性的技术 + - 依赖注入(Dependency Injection) + - Mocking框架 + - 测试替身技术 + - 伪造(Faking) + API的轻量级实现 + - 打桩(Stubbing) + 赋予函数行为的过程 + - 交互测试(Interaction Testing) + 验证函数调用行为及参数 + - 何时使用 + - 真实实现 + - 执行时间快 + - 确定性的 + - 实例构造简单 + - 伪造(Faking) + - 权衡维护成本与收益 + - 当真实实现不可用时 + - 考虑伪造的保真度 + - 需要写测试 + - 打桩(Stubbing) + - 谨慎使用打桩 + - 可能会让测试意图不清晰 + - 可能让测试变得脆弱 + 测试代码包含了实现细节 + - 可能让测试变得低效 + 难以测试有副作用的状态 + - 在真实实现和伪造不可用时 + - 少量函数仅依赖返回值时 + - 交互测试(Interaction Testing) + - 容易让测试变得脆弱 + 测试代码包含了实现细节 + 尽可能测试状态而非交互 + - 需测试函数副作用时使用 + - 需测试函数调用次序时使用 + - 不要过度测试 + 仅测试必须测试的信息 +``` + +测试替身能通过一些模拟或伪造的技术来控制被测试代码的执行路径,比如在OOP中我们可以通过接口的多个实现,来完成生产代码与测试代码的不同实现。 + +由于测试替身技术本身非常成熟,所以本文不做基本的介绍,推荐阅读这篇文章进一步了解: + +- [TestDouble](https://martinfowler.com/bliki/TestDouble.html) + +在Google的多年实践中发现,测试替身很容易被滥用,造成很多脆弱的测试,而被滥用最多的就是打桩(Stubbing)技术。不同替身技术都有其适用场景,推荐的一个决策流程是: + +- 如果生产代码的执行时间足够快,那就不需要替身技术,直接测试生产代码; +- 如果伪造(Faking)的实现成本很低,且伪造的保真度够高(能尽可能模拟真实的使用场景),则推荐使用伪造替身技术; +- 如果在前两者都不可用的情况下,仅被测试代码只依赖少量函数或方法的返回值时,可以使用打桩(Stubbing)替身技术; +- 交互测试(Interaction Testing)替身技术谨慎使用,如果要用也仅在需测试函数副作用或调用次序时使用,并且不要过度测试不必要的数据; + +### 较大型的测试(Larger Testing) + +```markmap +- 较大型的测试 + - 为什么需要 + - 单元测试保真度的问题 + - 环境配置的问题 + - 负载下的问题 + - 预期外的行为与副作用 + - 紧急行为和真空效应 + - 意外的修改 + - 单元测试覆盖不到的位置 + - 挑战 + - 维护与归属的问题 + - 缺乏开发标准 + - 结构 + - SUT + - 契约测试(Contract test) + - 测试数据 + - 种子数据 + - 测试流量 + - 采样数据 + - 手工数据 + - 真实基线 + - 验证 + - 手工 + - 断言 + - 类型 + - 功能性测试 + - 浏览器和设备测试 + - 性能测试 + - 部署配置测试 + - 探索性测试 + - Bug Bash + - A/B测试 + - 用户验收测试(UAT) + - 混沌测试(Chaos) + - 工作流 + - 编写 + - 运行 + - 加速测试 + - 降低内部系统超时和延迟 + - 优化测试构建时 + - 驱除松散性 + - 密封的SUT环境 + - 使测试易于理解 + - 清晰的故障定位信息 + - 尽可能减少定位问题的成本 + 如日志打印分布式追踪ID而非堆栈信息 + - 提供支持和联系信息 + - 维护 +``` + +在测试金字塔的顶端是占比只有20%的集成测试与E2E测试,虽然占比少,但其却可解决单元测试的以下问题: + +- 保真度的问题:单元测试因使用测试替身来加速执行时间,但替身与实现本身就存在保真度的问题,一旦被替身的实现发生改变,单元测试因模拟行为未变,可能造成一些意想不到的Bugs。 +- 环境配置的问题:环境的问题只能在接近生产环境的测试环境(如UAT)环境中去测试与发现问题,这是单元测试无法覆盖的测试范围。如Google的一些重大全球性的Bug都和环境配置问题有关系。 +- 负载下的问题:在压力测试下,系统的行为表现如何?性能是否能达到业务要求?这类非功能性的需求测试只能在E2E测试中完成。 +- 预期外的行为与副作用:单元测试是在开发者预期的视角下完成的,所以存在一定的视角盲区。在一个接近生产环境的测试环境测试是发现这类问题最好的办法。 +- 紧急行为和真空效应:如果系统的运行时环境发生一些意外的修改,如集群网络配置或部署配置发生变更,这类问题也只能在集成环境中发现。 + +较大型测试的编写与维护都是成本高昂的,在我们项目实践中,一般和业务系统强相关的集成测试和部分E2E测试都是业务开发团队完成的。但一些公共的E2E测试,比如某个全局性的功能性测试,可能由一个独立的小组完成,也可能只完成一个MVP的版本,之后由业务系统维护团队开发完成。 + +推荐进一步阅读的文章: + +- [浅谈契约测试](https://insights.thoughtworks.cn/contract-testing/) +- [契约测试之核心解惑](https://ariman.cn/2019/05/19/契约测试之核心解惑/) + +## 弃用(Deprecation) + +```markmap +- Deprecation + - 为什么 + - 代码是负债而非资产 + 因为需要维护 + - 过时的系统需要弃用 + - 维护成本太高 + - 功能无用、重复或被替代 + - 旧不意味着过时 + - 代码越少,功能越多 + - 困难 + - 系统用户越多,越难弃用 + - 系统启用,代码保留 + - 被弃用的,和没有准备好的 + - 演进而非弃用 + - 在设计时考虑弃用 + - 谨慎启动新项目 + - 类型 + - 咨询 + - 强制 + - 警告 + - hope is not a strategy + - alert fatigue + - 流程 + - Owner + - 里程碑 + - 工具 + - How/By who + - Code Search + - Testing +``` + +代码是资产还是负债?Google的答案是负债,因为代码需要不断的维护才能正常工作。负债是有高昂的利息,降低负债最好的办法就是在不需要的时候砍掉它。而这就是弃用过程的价值。 + +对开发人员来说,弃用是个难以接受的过程,因为幸苦写的代码,很难下定决心去销毁它。所以一个中庸之道是在代码将要被弃用前,想办法通过演进的方式给予其二次生命。如果非要弃用,也只是停止维护和运行,旧的代码依旧会在代码仓库中可被搜索到,历史记录也会被保留。 + +我的个人项目实践是,下线一个系统是一件需要重视的过程。一个系统一旦被发布,它被使用的场景就很难以想象,API的用户可能会以意想不到的方式去使用它。所以尽可能通过代码搜索去找到其被使用的场景,之后再给充足的Deadline广而告之,甚至可以主动与用户沟通,确保不会让其出现大的损失。 + +## 总结 + +代码可能只会被写一次,但会被读很多次。所以软件工程中的过程部分主要致力于解决代码可读性的问题。无论是风格指南、代码评审、文档甚至自动化测试,很大程度上都在为提高代码可读性。 + +写代码很容易,能写出易懂的代码却有难度。所以从这个角度看,写代码是个入门简单精通却难的技能,需要我们不断的精进,通过多种实践去提高这个技能。希望这篇文章能让你对写代码这件事有更多的理解。 diff --git a/content/dev/software-engineering-at-google/the-test-pyramid.excalidraw b/content/dev/software-engineering-at-google/the-test-pyramid.excalidraw new file mode 100644 index 000000000..efa438d76 --- /dev/null +++ b/content/dev/software-engineering-at-google/the-test-pyramid.excalidraw @@ -0,0 +1,878 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor", + "elements": [ + { + "id": "sSVbJW3C-g7Rq-3z71dQO", + "type": "line", + "x": 820.8984375, + "y": 284.4375, + "width": 272.328125, + "height": 472, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 1717100817, + "version": 54, + "versionNonce": 909781919, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -272.328125, + 472 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "pEGcDne346VA7xq4fgz4d", + "type": "line", + "x": 822.6328125, + "y": 286.375, + "width": 270.10546875, + "height": 468, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 19778481, + "version": 32, + "versionNonce": 42410449, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 270.10546875, + 468 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "FiD_PAR1_1clJDP4y5EoX", + "type": "line", + "x": 549.7578125, + "y": 756.9140625, + "width": 540.83984375, + "height": 0, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 1035445727, + "version": 59, + "versionNonce": 642867135, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 540.83984375, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "LZP_75Xrhm6XDqvibBdwM", + "type": "line", + "x": 781.2253643263132, + "y": 360.7265625, + "width": 83.09651950597754, + "height": 0, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 772868337, + "version": 149, + "versionNonce": 700759985, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 83.09651950597754, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "Z8Jm8Cqw2Bq7MiJ-Y3PQa", + "type": "line", + "x": 737.0859375, + "y": 432.1328125, + "width": 170.44140625, + "height": 0, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 685741439, + "version": 68, + "versionNonce": 1370296287, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 170.44140625, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "gXJRm9ompqmDdXDwLz5FI", + "type": "text", + "x": 728.78515625, + "y": 222.4921875, + "width": 190, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 2046218801, + "version": 168, + "versionNonce": 928227729, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "text": "Auto Test Pyramid", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "Auto Test Pyramid" + }, + { + "id": "x3ZhV0L6slDewPLOtoTf5", + "type": "text", + "x": 760.7453209100577, + "y": 585.26896402189, + "width": 149, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 1834072337, + "version": 147, + "versionNonce": 1381874687, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "text": "Unite Tests\n(single process)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 43, + "containerId": null, + "originalText": "Unite Tests\n(single process)" + }, + { + "type": "text", + "version": 163, + "versionNonce": 1483425649, + "isDeleted": false, + "id": "S_NjunrDM53uQQP4c-Ywm", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 763.79296875, + "y": 389.99609375, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 116.97265625, + "height": 16.067672561813193, + "seed": 1679470943, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999226, + "link": null, + "locked": false, + "fontSize": 12.854138049450555, + "fontFamily": 1, + "text": "Integration Tests", + "baseline": 11.067672561813193, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Integration Tests" + }, + { + "id": "vXAJBQQieLixAcKxLI6fG", + "type": "text", + "x": 804.2083726377354, + "y": 313.8413529778893, + "width": 33, + "height": 27, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 2026737215, + "version": 211, + "versionNonce": 744743967, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "text": "E2E\nTests", + "fontSize": 10.69737723214286, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 23, + "containerId": null, + "originalText": "E2E\nTests" + }, + { + "id": "PMYbOwOOl7taOwriwv5Rd", + "type": "arrow", + "x": 1126.0119994182967, + "y": 291.8982991456986, + "width": 0.09298971503062603, + "height": 427.5730787444114, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 1121410815, + "version": 168, + "versionNonce": 659763537, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -0.09298971503062603, + 427.5730787444114 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "VF866nBBSuVLXnBFOzLll", + "focus": 0.13764880952380953, + "gap": 12.691267895698616 + }, + "endBinding": { + "elementId": "_h6pn_xe-HGGbXtgTgXnY", + "focus": -0.13509114583333334, + "gap": 8.942684609889966 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "VF866nBBSuVLXnBFOzLll", + "type": "text", + "x": 1099.68359375, + "y": 254.20703125, + "width": 61, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 1582879071, + "version": 74, + "versionNonce": 263194687, + "isDeleted": false, + "boundElements": [ + { + "id": "PMYbOwOOl7taOwriwv5Rd", + "type": "arrow" + } + ], + "updated": 1660131999226, + "link": null, + "locked": false, + "text": "Slower", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "Slower" + }, + { + "id": "_h6pn_xe-HGGbXtgTgXnY", + "type": "text", + "x": 1096.5078125, + "y": 728.4140625, + "width": 68, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 1308401201, + "version": 57, + "versionNonce": 157474609, + "isDeleted": false, + "boundElements": [ + { + "id": "PMYbOwOOl7taOwriwv5Rd", + "type": "arrow" + } + ], + "updated": 1660131999226, + "link": null, + "locked": false, + "text": "Faster", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "Faster" + }, + { + "id": "6FezFWypVvibeROGDmltZ", + "type": "text", + "x": 979.66796875, + "y": 557.609375, + "width": 49, + "height": 25, + "angle": 1.0399241303226852, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 164796703, + "version": 256, + "versionNonce": 1512220767, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999226, + "link": null, + "locked": false, + "text": "80%", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "80%" + }, + { + "type": "text", + "version": 341, + "versionNonce": 1291964689, + "isDeleted": false, + "id": "4A8bltC_tXntSI1jXj-LA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 1.0399241303226852, + "x": 879.9531249999999, + "y": 375.6406249999999, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 38, + "height": 25, + "seed": 954370609, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999226, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "15%", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "15%" + }, + { + "type": "text", + "version": 396, + "versionNonce": 1502364799, + "isDeleted": false, + "id": "u83N8atryAlw4FFowm3R2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 1.0399241303226852, + "x": 842.97265625, + "y": 304.34765625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 32, + "height": 25, + "seed": 278136575, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999227, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "5%", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "5%" + }, + { + "type": "text", + "version": 446, + "versionNonce": 128419569, + "isDeleted": false, + "id": "7Ip6WVPKVJ8CkDpiiJ6f_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 772.1563414348082, + "y": 407.2565364453433, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 106, + "height": 18, + "seed": 823143551, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999227, + "link": null, + "locked": false, + "fontSize": 14.34767576863472, + "fontFamily": 1, + "text": "(single machine)", + "baseline": 13, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "(single machine)" + }, + { + "type": "text", + "version": 1095, + "versionNonce": 1303207071, + "isDeleted": false, + "id": "P1p4lAtuy4rw1G7l7B7tl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 767.7552061030044, + "y": 343.38573832560735, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 106.39826862948642, + "height": 14.846270041323669, + "seed": 252421521, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999227, + "link": null, + "locked": false, + "fontSize": 11.833859384805969, + "fontFamily": 1, + "text": "(multiple machines)", + "baseline": 10.846270041323669, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "(multiple machines)" + }, + { + "id": "J0S3QOpW7rimA4ATfEV7_", + "type": "text", + "x": 1146.0832823135563, + "y": 314.49829699598695, + "width": 263, + "height": 125, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "seed": 1209939935, + "version": 219, + "versionNonce": 870023377, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999227, + "link": null, + "locked": false, + "text": "- more complex\n- higher costs\n- slower execute time\n- more integration required\n- less reliable", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 118, + "containerId": null, + "originalText": "- more complex\n- higher costs\n- slower execute time\n- more integration required\n- less reliable" + }, + { + "type": "text", + "version": 343, + "versionNonce": 708210879, + "isDeleted": false, + "id": "b0TVDTcBtm-2c6ZZTLkuw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1146.628021387079, + "y": 585.6238509858254, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 258, + "height": 125, + "seed": 82247263, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999227, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "- simple tests\n- lower costs\n- quick execute time\n- less integration required\n- more reliable", + "baseline": 118, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- simple tests\n- lower costs\n- quick execute time\n- less integration required\n- more reliable" + }, + { + "id": "hvaSr2980aML8j5UTkbVE", + "type": "line", + "x": 741.3340444698583, + "y": 349.7225363641757, + "width": 56.12932697575434, + "height": 40.983719073141515, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 1718339647, + "version": 144, + "versionNonce": 255147697, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999227, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 56.12932697575434, + -40.983719073141515 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "L1LwU10D75ue7b_mX-U3G", + "type": "line", + "x": 738.1656280441985, + "y": 271.342120913913, + "width": 56.67446995932153, + "height": 36.335547990132625, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "round", + "seed": 1438208703, + "version": 296, + "versionNonce": 415760607, + "isDeleted": false, + "boundElements": null, + "updated": 1660131999227, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 56.67446995932153, + 36.335547990132625 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "text", + "version": 653, + "versionNonce": 945569937, + "isDeleted": false, + "id": "vvq28BSyKSGTGm4AuWTZE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 616.7554826297848, + "y": 265.9205293474675, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 133, + "height": 78, + "seed": 1953577439, + "groupIds": [ + "g-Cblu9Ls2RdTxmHqu4qK" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660131999227, + "link": null, + "locked": false, + "fontSize": 12.69950664491254, + "fontFamily": 1, + "text": "- UI tests\n- functional tests\n- load tests\n- UAT\n- exploratory testing", + "baseline": 73, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- UI tests\n- functional tests\n- load tests\n- UAT\n- exploratory testing" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/content/dev/software-engineering-at-google/tool.zh-cn.md b/content/dev/software-engineering-at-google/tool.zh-cn.md new file mode 100644 index 000000000..b6d762a5a --- /dev/null +++ b/content/dev/software-engineering-at-google/tool.zh-cn.md @@ -0,0 +1,33 @@ +--- +title: "Google软件工程之工具篇" +date: 2022-08-19 +draft: true +tags: ["软件工程", "Branch Management", "Build System", "Code Search", "Static Analysis", "Dependency Management", "Continuous Integration", "Continuous Delivery"] +keywords: "" +description: "本文是《Software Engineering at Google》的读书笔记,同时会穿插分享我对软件工程的理解。本文主要介绍软件工程的相关工具,主要包括版本控制、分支管理、代码搜索、构建系统、代码静态化分析、依赖管理与CI/CD等。" +isCJKLanguage: true +og_image: "" +categories: [ + "软件工程" +] +markmap: + enabled: true + id: "software-engineering-at-google-tool" +--- + +> 以下是《Software Engineering at Google》一书第四部分工具篇的思维导图,由于此部分占全书近40%,所以本文不会详细的介绍其中的概念,想详细了解的读者建议阅读原书。本文会结合此书这部分内容分享作者的个人理解及相关经验。 + +```markmap +# Google软件工程 +## 文化(Culture) +## 过程(Process) +## 工具(Tool) +- Version control +- Branch management +- Code search +- Build system +- Static analysis +- Dependency management +- Continuous integration +- Continuous delivery +``` diff --git a/content/dev/tech-stack-of-side-project/index.zh-cn.md b/content/dev/tech-stack-of-side-project/index.zh-cn.md index 739d40e69..a9e9e8be2 100644 --- a/content/dev/tech-stack-of-side-project/index.zh-cn.md +++ b/content/dev/tech-stack-of-side-project/index.zh-cn.md @@ -6,6 +6,9 @@ tags: ["编程语言", "基础设施", "SaaS", "监控分析", "技术栈", "Inf keywords: "" description: "本文分享我的个人项目技术栈,包括编程语言、框架与库、数据库、基础设施、CI/CD、监控分析、设计及常用的SaaS服务。" isCJKLanguage: true +categories: [ + "个人成长" +] --- --- diff --git a/content/dev/time-in-distributed-system/index.zh-cn.md b/content/dev/time-in-distributed-system/index.zh-cn.md index a0ad9bb1c..27a1db503 100644 --- a/content/dev/time-in-distributed-system/index.zh-cn.md +++ b/content/dev/time-in-distributed-system/index.zh-cn.md @@ -8,7 +8,7 @@ description: "本文梳理在分布式系统中时间对事件序列的影响, isCJKLanguage: true og_image: "https://img.bmpi.dev/3d3413ee-7024-5ff7-5904-e9120dd5690f.png" categories: [ - "什么是X" + "分布式技术" ] markmap: enabled: true diff --git a/content/dev/vscode-on-cloud/index.zh-cn.md b/content/dev/vscode-on-cloud/index.zh-cn.md index 26a0c8d33..7ee3a3d46 100644 --- a/content/dev/vscode-on-cloud/index.zh-cn.md +++ b/content/dev/vscode-on-cloud/index.zh-cn.md @@ -7,6 +7,9 @@ keywords: "" description: "本文分享笔者基于AWS与Pulumi搭建类GitHub Codespaces的云端VSCode的经验。" isCJKLanguage: true og_image: "https://img.bmpi.dev/dafdc38a-8e97-7daa-d860-4ad78c4d182b.png" +categories: [ + "什么是X" +] --- - [云端 IDE](#云端-ide) diff --git a/content/dev/what-markdown-can-do/index.zh-cn.md b/content/dev/what-markdown-can-do/index.zh-cn.md index 37a71988c..0225691b1 100644 --- a/content/dev/what-markdown-can-do/index.zh-cn.md +++ b/content/dev/what-markdown-can-do/index.zh-cn.md @@ -6,6 +6,9 @@ tags: ["Markdown", "排版", "LaTex"] keywords: "Markdown" description: "本文介绍了零成本用Markdown搞定博客网站、笔记文档、演讲胶片与年终总结报告,彻底抛弃Word与PPT" isCJKLanguage: true +categories: [ + "技术写作" +] --- 你是否遇到这些问题:写报告需要打开Word/PPT,每次浪费不少时间在排版上?写博客需要在管理后台网页里排版?在这篇文章里我将会介绍如何使用一种纯文本标记语言Markdown去排版各类型文档。 diff --git a/content/invest.zh-cn.md b/content/invest.zh-cn.md index 0e3ac1be5..7c126d054 100644 --- a/content/invest.zh-cn.md +++ b/content/invest.zh-cn.md @@ -38,4 +38,4 @@ outputs = "html" ## 双均线交易信号邮件订阅 -- 你可以在这里邮件订阅 [双均线交易策略信号](https://money.i365.tech/) +- 你可以在这里邮件订阅 [双均线交易策略信号](https://money.bmpi.dev/) diff --git a/content/link.zh-cn.md b/content/link.zh-cn.md index 2c9f46481..9125a1d1a 100644 --- a/content/link.zh-cn.md +++ b/content/link.zh-cn.md @@ -21,13 +21,6 @@ outputs = "html" | [ZoomQuiet](https://blog.zoomquiet.io/) | 是也乎( ̄▽ ̄) | | [认知.xKnow](https://blog.xknow.net/) | 认知.xKnow | | [刘悦的技术博客](https://v3u.cn/) | 刘悦的技术博客 | - -## 我关注的博客 - -| | | -| -- | -- | -| [面向信仰编程](https://draveness.me/) | Go语言设计与实现 | -| [酷壳](https://coolshell.cn/) | 享受编程和技术所带来的快乐 | | [麻广广的博客](https://maguangguang.xyz/) | 关注领域:企业架构设计(分布式架构设计、微服务架构设计),企业数字化转型,自我提升及个人成长,读书/学习思考笔记等 | | [少个分号](https://shaogefenhao.com/) | 软件架构、高认知软件工程、DDD、领域驱动设计、敏捷、研发自测、敏捷团队 | | [爱码叔-iCodeBook](https://icodebook.com/) | 专注于软件设计、系统架构 | @@ -37,3 +30,11 @@ outputs = "html" | [于晓南](https://qualityfocus.club/yxn) | 关注软件质量 | | [聂子云](http://www.niezitalk.com/) | 聂子云 | | [张凯峰](https://www.kaifengzhang.com/) | 张凯峰 | + +## 我关注的博客 + +| | | +| -- | -- | +| [面向信仰编程](https://draveness.me/) | Go语言设计与实现 | +| [酷壳](https://coolshell.cn/) | 享受编程和技术所带来的快乐 | +| [Limboy](https://limboy.me/) | Limboy | \ No newline at end of file diff --git a/content/money/invest-alchemy.zh-cn.md b/content/money/invest-alchemy.zh-cn.md new file mode 100644 index 000000000..2bb92c97e --- /dev/null +++ b/content/money/invest-alchemy.zh-cn.md @@ -0,0 +1,81 @@ +--- +title: "投资炼金术" +date: 2022-09-12 +draft: false +tags: ["投资组合", "交易策略", "资金策略", "风险评估", "交易评测"] +keywords: "投资炼金术" +description: "投资炼金术系统支持多种投资交易策略,可评估用户的投资交易水平,自动计算用户投资组合的净值,从而提升用户的投资交易水平。" +isCJKLanguage: true +--- + +## 背后的故事 + +在2016年的时候,我曾开发过一款App名为
[交易日记](/money/build-trade-system/),这个App是根据我自身对投资交易的需求而打造,之所以有这些需求是因为我经历了黑暗的A股2015年股灾。当时我的投资仓位出现了很大的亏损,甚至参与创业的公司也因此而倒闭,所以花费了累计半年的时间学习[投资交易原理相关的书籍](/money/passive-income-protfolio/202007/)。 + +之后有了交易日记App的前身,是一个Excel表格: + +![](https://img.bmpi.dev/815c5166-a61f-1625-401f-80c695979bb7.png) + +这个Excel是由VBA开发的,甚至对接了东方财富Choice软件的接口去获取股价等数据。当时我的注意力还放在股票投资交易上。但经过半年的实操后,发觉股票投资并不适合业余投资交易的人。就算能盈利也需要耗费非常多的心力,所以之后我逐渐将注意力放在了指数基金上。在18年开始,受到一本书的启发,我决定做一个长期的投资组合,名为[被动收入投资组合](/money/passive-income-protfolio/202006/)。 + +被动收入投资组合是一个完全由指数基金组成的投资组合,它类似
FoF基金。我为此开发了另外一套更简单的Excel模版来监控整个投资组合的收益与风险指标。 + +![](https://img.bmpi.dev/7ea206be-409c-d2a2-bd39-7b618e3ba345.png) + +在长达几年的实操中,我逐渐青睐技术分析中的[双均线投资策略](/money/passive-income-protfolio/202008/),为此开发了一个策略提醒的小工具,这个工具每天会自动将投资交易的买卖信号通过邮件的方式去提醒交易者。 + +![](https://img.bmpi.dev/ed73c1ad-0a1c-cd37-7b10-250ae3cdd56b.png) + +在这个工具持续运行的时间里,我一直在思考它的演进方向。直到今年才有了一些想法: + +![](https://img.bmpi.dev/179d4fd0-018a-9fe0-a432-8dc8cf5d7fdd.png) + +最终这些想法变成具体的一些任务及代码: + +![](https://img.bmpi.dev/898d87a6-f801-e6e5-8510-c8791a3b9da5.png) + +在肝了近百小时后,这个系统上线了: + +[money.bmpi.dev](https://money.bmpi.dev/) + +我給它起了一个很俗的名字:
投资炼金术。 + +## [投资炼金术](https://money.bmpi.dev/) + +从功能上来讲,投资炼金术可以做下面的事情: + +- 策略:策略提醒功能,能支持多种交易策略的扩展。 + - Double MA:双均线策略,已经上线。 + - Turtle:海龟交易策略。 + - Backtrace MA250:回踩年线策略。 + - Breakthrough Platform:平台突破策略。 + - 其他。 +- 市场 + - 市场热度监控:比如全市场历史PE/PB的区间,开户人数的变化等,这些数据的变化和趋势本身也可以作为交易策略的输入。 + - 全球指数的历史数据:这些数据主要给投资组合表现评测做对比,也可以生成投资组合与全球主流指数对比的走势图。 +- 投资组合:以基金计算净值的方式衡量投资组合的表现。 + - 通过历史交易、资金流水、历史持仓台账的数据计算出组合的净值。当前可支持A股ETF/LOF等场内基金、股票等投资标的,场外公募基金未来会支持的。 +- 交易 + - 用户:支持用户的投资组合净值的计算。 + - 机器人:机器人可以自动追踪某个交易策略,根据资金策略自动维护某个投资组合。这比简单的回测要更系统和真实,可以看到每天的交易记录和持仓记录,还有资金的变化记录等。 +- Web界面 + - 自动展示某个投资组合的收益走势图及全球主流指数的对比,还可以看到投资组合的风险状况,及交易、持仓和资金记录。 +- 通知 + - 邮件通知 + - Telegram Channel通知 + +一些系统界面截图: + +![](https://img.bmpi.dev/7360956a-1c30-77a2-e9a2-fd4db6f03683.png) + +目前投资炼金术只上线了三款投资组合,前两款都是机器人跟随双均线交易策略的投资组合,第三个是被动收入投资组合。 + +![](https://img.bmpi.dev/e7eda067-5c2a-4fa3-05aa-2df0ca94ad45.png) + +## 未来的规划 + +投资炼金术的主体功能大概完成了八成左右,剩下的就是围绕此做的一些修补,最多的还是如何开发不同的投资策略及机器人,用来方便的追踪不同交易策略对投资组合收益与风险的影响。 + +当然也可以把真实用户的投资交易记录用这个系统生成投资组合并自动维护,也可以方便的知道自己的交易水平与全球主流指数相比到底相差几何,我相信这种可视化的风险监控方式对我们长期投资来说是有益处的。 + +最后这套系统的代码是[开源](https://github.com/bmpi-dev/invest-alchemy)的,这意味着你可以按照自己的想法去开发,也可以免费的自用。当然如果你不想花时间折腾这些,但是又想用这种方式去量化理解自己的投资组合,那也可以[与我联系](/about/)。 diff --git a/content/money/passive-income-protfolio/202008.zh-cn.md b/content/money/passive-income-protfolio/202008.zh-cn.md index 6f88a4612..8321f0f54 100644 --- a/content/money/passive-income-protfolio/202008.zh-cn.md +++ b/content/money/passive-income-protfolio/202008.zh-cn.md @@ -50,7 +50,7 @@ isCJKLanguage: true 看完以上视频后如果觉得双均线交易策略还不错的话,那么你可能需要这个工具: -[Invest Alchemy](https://money.i365.tech/) +[Invest Alchemy](https://money.bmpi.dev/) ![](https://img.bmpi.dev/fb53992f-9bd7-d241-e0c3-f748c5837072.png) diff --git a/content/money/passive-income-protfolio/202009.zh-cn.md b/content/money/passive-income-protfolio/202009.zh-cn.md index 16ec55b38..9e716682b 100644 --- a/content/money/passive-income-protfolio/202009.zh-cn.md +++ b/content/money/passive-income-protfolio/202009.zh-cn.md @@ -42,7 +42,7 @@ isCJKLanguage: true 从双均线交易信号看,目前大多数指数基金都处于空仓仓位,如果你采用此交易策略,那可以躲过这次大的跌幅。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 大跌时我们能做什么 diff --git a/content/money/passive-income-protfolio/202010.zh-cn.md b/content/money/passive-income-protfolio/202010.zh-cn.md index 89a28c2e2..5c0e96d1e 100644 --- a/content/money/passive-income-protfolio/202010.zh-cn.md +++ b/content/money/passive-income-protfolio/202010.zh-cn.md @@ -38,7 +38,7 @@ isCJKLanguage: true 从双均线交易信号看,目前大多数指数基金都处于持仓仓位,但是由于市场处于震荡整理行情,所以双均线会出现短期买入并卖出的信号干扰。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 投资交易的心理建设 diff --git a/content/money/passive-income-protfolio/202011.zh-cn.md b/content/money/passive-income-protfolio/202011.zh-cn.md index ca6daaace..b20ac10c7 100644 --- a/content/money/passive-income-protfolio/202011.zh-cn.md +++ b/content/money/passive-income-protfolio/202011.zh-cn.md @@ -38,7 +38,7 @@ isCJKLanguage: true 从双均线交易信号看,目前持有的大部分是估值处于中部的标的。从趋势交易的角度看目前值得持有的都是一些中低估值的标的。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 关于银行分期贷款的坑 diff --git a/content/money/passive-income-protfolio/202012.zh-cn.md b/content/money/passive-income-protfolio/202012.zh-cn.md index 75b1e4898..acea1ccfd 100644 --- a/content/money/passive-income-protfolio/202012.zh-cn.md +++ b/content/money/passive-income-protfolio/202012.zh-cn.md @@ -82,7 +82,7 @@ isCJKLanguage: true 如果你分析下双均线策略信号最近两个月的表现,会发现双均线在趋势行情中表现还是很不错的。对于高估值的标的如创业板、消费与医药,双均线在上月底还是空仓状态,躲过上个月的大跌,又吃到了这个月的大涨。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202101.zh-cn.md b/content/money/passive-income-protfolio/202101.zh-cn.md index 652a3b8f5..f5de4614a 100644 --- a/content/money/passive-income-protfolio/202101.zh-cn.md +++ b/content/money/passive-income-protfolio/202101.zh-cn.md @@ -121,7 +121,7 @@ H股ETF(510900.SH)于20210104日买入, 持有25天, 盈利5.45% 从双均线持仓的品种中可以看出创业板50、医药、原油、深100、环保、50AH、中国互联与中概互联这些品种持仓一个多月收益都超过10%了,当然也有下跌的品种,平均跌幅范围在-5%至-1%之间。双均线策略非常适合牛市这种大趋势行情,而之前我们在 [双均线交易策略](/money/passive-income-protfolio/202008/) 这篇文章中回测发现创业板很适合采用双均线策略,因为其波动性比较大,也比较有趋势性。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202102.zh-cn.md b/content/money/passive-income-protfolio/202102.zh-cn.md index bb2631127..ce99b7547 100644 --- a/content/money/passive-income-protfolio/202102.zh-cn.md +++ b/content/money/passive-income-protfolio/202102.zh-cn.md @@ -17,7 +17,7 @@ og_image: "https://img.bmpi.dev/28a8c22b-f064-8846-2f58-17f3d9749be2.png" 在使用了一段时间估值策略后,我发现估值策略并不适合作为一个单独的交易策略:如果通过计算一个标的的历史估值区间,然后将这个区间划分为多个区间段,在低估值区间段分批买入,高估值区间段分批卖出。这种交易策略本身是可以赚钱的,它的买入策略可以让我们在下跌大趋势时做左侧交易,卖出策略可以让我们在上涨大趋势时做右侧交易。这种策略宏观上是没错的,难的在于微观上操作时尤其是卖出时很容易错失最具爆发力的后期上涨阶段。以创业板、消费和医药指数为例,被动收入投资组合在估值便宜的时候一直持有,大概在估值达95-98%的时候开始卖出,实际上涨幅最猛烈的就属于98-100%的这个阶段,而且可能会长时间(比一般人想象的要久一些)在这个估值区间运行。 -后期我开始调整策略,转而在高估值区间采用趋势跟随交易策略(双均线),最终开发了 [双均线策略信号邮件提醒程序](https://money.i365.tech/)。 +后期我开始调整策略,转而在高估值区间采用趋势跟随交易策略(双均线),最终开发了 [双均线策略信号邮件提醒程序](https://money.bmpi.dev/)。 那么估值究竟是什么?能不能用估值作为交易策略?在这篇 [燕翔:树能不能长到天上去,谈估值天花板(国信策略)](https://mp.weixin.qq.com/s/2dNo-QXZ-ipkAQW44B8-NA) 文章中: @@ -117,7 +117,7 @@ H股ETF(510900.SH)于20210224日买入, 持有2天, 盈利-1.46% 双均线策略天生很适合在大趋势行情中捕捉大段的盈利区间,以创业板50()为例,双均线交易策略于[2020年12月11日发出买入信号](https://www.i365.tech/invest-alchemy/data/strategy/double-ma/20201211.txt)(买入价1.116),于[2021年02月26日发出卖出信号](https://www.i365.tech/invest-alchemy/data/strategy/double-ma/20210226.txt)(卖出价1.269),持仓77天盈利13.7%。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202103.zh-cn.md b/content/money/passive-income-protfolio/202103.zh-cn.md index b6dd66d9c..6b6eac89f 100644 --- a/content/money/passive-income-protfolio/202103.zh-cn.md +++ b/content/money/passive-income-protfolio/202103.zh-cn.md @@ -127,7 +127,7 @@ H股ETF(510900.SH)于20210305日卖出, 空仓28天, 空仓期涨幅-0.57% 双均线策略里很多品种都进入空仓状态,已经持仓的品种都已盈利,目前的市场行情并不好,可以继续等待更多的买入时机。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202104.zh-cn.md b/content/money/passive-income-protfolio/202104.zh-cn.md index a00c68011..f4b08cb89 100644 --- a/content/money/passive-income-protfolio/202104.zh-cn.md +++ b/content/money/passive-income-protfolio/202104.zh-cn.md @@ -118,7 +118,7 @@ H股ETF(510900.SH)于20210305日卖出, 空仓55天, 空仓期涨幅-3.18% 双均线策略再次捕捉到了医药和创业板的暴涨,持仓盈利近 10%。我在思考要不要在高估值的品种只采取双均线这类趋势跟随的策略?因为一般投资品种在高估值大多经历了暴涨,在这个阶段波动区间很大,趋势跟随策略的表现应该很好。 -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202105.zh-cn.md b/content/money/passive-income-protfolio/202105.zh-cn.md index 765680d16..6a6a65fd6 100644 --- a/content/money/passive-income-protfolio/202105.zh-cn.md +++ b/content/money/passive-income-protfolio/202105.zh-cn.md @@ -102,7 +102,7 @@ H股ETF(510900.SH)于20210305日卖出, 空仓84天, 空仓期涨幅-6.28% 纳指ETF(159941.SZ)于20210507日卖出, 空仓21天, 空仓期涨幅-0.92% ``` -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202106.zh-cn.md b/content/money/passive-income-protfolio/202106.zh-cn.md index e36619a98..bcdf2b779 100644 --- a/content/money/passive-income-protfolio/202106.zh-cn.md +++ b/content/money/passive-income-protfolio/202106.zh-cn.md @@ -330,7 +330,7 @@ H股ETF(510900.SH)于20210616日卖出, 空仓13天, 空仓期涨幅2.39% 黄金基金(518800.SH)于20210610日卖出, 空仓19天, 空仓期涨幅-4.05% ``` -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202107.zh-cn.md b/content/money/passive-income-protfolio/202107.zh-cn.md index c5bd8ad39..d3cba51d1 100644 --- a/content/money/passive-income-protfolio/202107.zh-cn.md +++ b/content/money/passive-income-protfolio/202107.zh-cn.md @@ -152,7 +152,7 @@ H股ETF(510900.SH)于20210707日卖出, 空仓23天, 空仓期涨幅-9.61% 中国互联(164906.SZ)于20210513日卖出, 空仓78天, 空仓期涨幅-28.04% 中概互联(513050.SH)于20210708日卖出, 空仓22天, 空仓期涨幅-7.66% ``` -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202108.zh-cn.md b/content/money/passive-income-protfolio/202108.zh-cn.md index 79126b776..340512417 100644 --- a/content/money/passive-income-protfolio/202108.zh-cn.md +++ b/content/money/passive-income-protfolio/202108.zh-cn.md @@ -139,7 +139,7 @@ H股ETF(510900.SH)于20210707日卖出, 空仓54天, 空仓期涨幅-11.51% 中概互联(513050.SH)于20210708日卖出, 空仓53天, 空仓期涨幅-13.23% ``` -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/202109.zh-cn.md b/content/money/passive-income-protfolio/202109.zh-cn.md index ceffd0089..eef4554f1 100644 --- a/content/money/passive-income-protfolio/202109.zh-cn.md +++ b/content/money/passive-income-protfolio/202109.zh-cn.md @@ -57,7 +57,7 @@ og_image: "https://img.bmpi.dev/e1ce3cfa-c724-42d6-8a28-d322bc70c86c.png" 军工ETF(512660.SH)于20210907日卖出, 空仓22天, 空仓期涨幅-7.53% ``` -> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/) +> 想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/) ### 组合表现 diff --git a/content/money/passive-income-protfolio/2021Q4.zh-cn.md b/content/money/passive-income-protfolio/2021Q4.zh-cn.md index 76bdd044d..39a4fb3e4 100644 --- a/content/money/passive-income-protfolio/2021Q4.zh-cn.md +++ b/content/money/passive-income-protfolio/2021Q4.zh-cn.md @@ -79,7 +79,7 @@ og_image: "https://img.bmpi.dev/8f81177f-470f-5878-6381-e2993db90e0a.png" 经过上述交易分析,我并不适合估值交易策略。所以之后的交易策略统一为双均线交易策略。但我会对双均线策略做一定的优化,旨在降低双均线策略在震荡行情中的频繁交易。 -我也会对 [双均线策略信号提醒系统](https://money.i365.tech/) 做一定的优化,修复已知的 Bugs。 +我也会对 [双均线策略信号提醒系统](https://money.bmpi.dev/) 做一定的优化,修复已知的 Bugs。 ## 组合季报 @@ -93,7 +93,7 @@ og_image: "https://img.bmpi.dev/8f81177f-470f-5878-6381-e2993db90e0a.png" 投资市场:中国、香港及美国。 -交易策略:[双均线策略](/money/passive-income-protfolio/202008/)。想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/)。 +交易策略:[双均线策略](/money/passive-income-protfolio/202008/)。想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/)。 资金管理:分散投资,买入标的单个不超过总资金 20%,单类(同一行业)标的持仓总市值不超过总资金 30%。 diff --git a/content/money/passive-income-protfolio/2022Q1.zh-cn.md b/content/money/passive-income-protfolio/2022Q1.zh-cn.md index f3072a390..5a9cc23a5 100644 --- a/content/money/passive-income-protfolio/2022Q1.zh-cn.md +++ b/content/money/passive-income-protfolio/2022Q1.zh-cn.md @@ -59,7 +59,7 @@ og_image: "https://img.bmpi.dev/9e8d58a1-5ff1-c6ed-135d-a5f76e7366d0.png" 投资市场:中国、香港及美国。 -交易策略:[双均线策略](/money/passive-income-protfolio/202008/)。想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/)。 +交易策略:[双均线策略](/money/passive-income-protfolio/202008/)。想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/)。 资金管理:分散投资,买入标的单个不超过总资金 20%,单类(同一行业)标的持仓总市值不超过总资金 30%。 diff --git a/content/money/passive-income-protfolio/2022Q2.zh-cn.md b/content/money/passive-income-protfolio/2022Q2.zh-cn.md index 536e33fa3..89ddee4aa 100644 --- a/content/money/passive-income-protfolio/2022Q2.zh-cn.md +++ b/content/money/passive-income-protfolio/2022Q2.zh-cn.md @@ -17,7 +17,7 @@ og_image: "https://img.bmpi.dev/b2c19910-b83c-94a0-7153-9fc938fa74cf.png" 之前写过一篇[长期投资之难](/money/passive-income-protfolio/202103/)的文章,介绍了看似简单的投资行为要长期坚持,却是件很难的事情。本期就交易之难,分享下我的个人经验。[被动收入投资组合](/categories/被动收入投资组合/)的交易策略是[双均线系统](/money/passive-income-protfolio/202008/),这是一个简单到几分钟就可以解释清楚的交易策略,但要用好却并不简单。 -以本季度双均线交易策略发出的买入卖出信号来分析(数据来源于[交易信号邮件提醒](https://money.i365.tech/)系统): +以本季度双均线交易策略发出的买入卖出信号来分析(数据来源于[交易信号邮件提醒](https://money.bmpi.dev/)系统): ```text 4月1日: @@ -152,7 +152,7 @@ og_image: "https://img.bmpi.dev/b2c19910-b83c-94a0-7153-9fc938fa74cf.png" 投资市场:中国、香港及美国。 -交易策略:[双均线策略](/money/passive-income-protfolio/202008/)。想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.i365.tech/)。 +交易策略:[双均线策略](/money/passive-income-protfolio/202008/)。想接受每日ETF指数基金双均线交易信号提醒,可在此订阅: [交易信号邮件提醒](https://money.bmpi.dev/)。 资金管理:分散投资,买入标的单个不超过总资金 20%,单类(同一行业)标的持仓总市值不超过总资金 30%。 diff --git a/content/project.zh-cn.md b/content/project.zh-cn.md index 6e0512973..5e5a9e111 100644 --- a/content/project.zh-cn.md +++ b/content/project.zh-cn.md @@ -19,27 +19,25 @@ outputs = "html" | | | | -- | -- | -| [Invest Alchemy](https://github.com/bmpi-dev/invest-alchemy) | 被动收入投资助手 | -| [scrape_google_keyword](https://github.com/bmpi-dev/scrape_google_keyword) | 刮取Google关键词Top10排名网站信息 | +| [投资炼金术](https://money.bmpi.dev/) | 投资交易助手 | +| [free4.chat](https://github.com/madawei2699/free4chat) | 匿名语音聊天室 | | [tg2web](https://github.com/bmpi-dev/tg2web) | 电报频道静态化工具 | -| [git2pdf](https://github.com/bmpi-dev/git2pdf) | 打印GitHub README文档 | | [awesome-seo](https://github.com/madawei2699/awesome-seo) | 我整理的关于Google SEO系统学习的收藏资源及通过流量变现的介绍 | | [notion-sites](https://github.com/madawei2699/notion-sites) | 我整理的Notion好站列表 | | [xian-IT](https://github.com/madawei2699/xian-IT) | 如果你对西安互联网公司熟悉,欢迎提交 Issue 或者PR,期待你的参与。 | -| [logseq.xyz](https://github.com/bmpi-dev/logseq.xyz) | Logseq 自托管版本(v0.5.5) | -| [free4.chat](https://github.com/madawei2699/free4chat) | 匿名语音聊天室 | -## 流量站 +## SEO流量站 | | | | -- | -- | | [gitopx.com](https://www.gitopx.com) | GitHub Top100 用户/仓库排名信息 | | [webtg.org](https://www.webtg.org) | Web Telegram | -| [instaghub](https://github.com/bmpi-dev/instaghub) | Instagram流量站 | ## 归档 | | | | -- | -- | -| [一款产品的从0到1之旅](/dev/zero-to-one/) | 交易日记iOS App[已下线] | -| [项目Google邮件列表](https://groups.google.com/forum/#!forum/qunpin) | 群品电子书项目[已下线] | +| [logseq.xyz](https://github.com/bmpi-dev/logseq.xyz) | Logseq 自托管版本(v0.5.5) | +| [instaghub](https://github.com/bmpi-dev/instaghub) | Ins Viewer | +| [一款产品的从0到1之旅](/dev/zero-to-one/) | 交易日记 iOS App | +| [项目Google Groups](https://groups.google.com/forum/#!forum/qunpin) | 群品电子书项目 | diff --git a/content/self/build-personal-knowledge-system/index.zh-cn.md b/content/self/build-personal-knowledge-system/index.zh-cn.md index 8493ee074..2fe997af2 100644 --- a/content/self/build-personal-knowledge-system/index.zh-cn.md +++ b/content/self/build-personal-knowledge-system/index.zh-cn.md @@ -8,6 +8,9 @@ description: "如何建立个人终身学习知识体系?如何自我管理? series: ["自我提升"] isCJKLanguage: true og_image: "https://img.bmpi.dev/bf1093ef-68c2-c92a-0de5-a08decb02b31.png" +categories: [ + "个人成长" +] --- - [为何要建立个人终身学习知识体系](#为何要建立个人终身学习知识体系) @@ -24,6 +27,7 @@ og_image: "https://img.bmpi.dev/bf1093ef-68c2-c92a-0de5-a08decb02b31.png" - [微信/电报群](#微信电报群) - [1 对 1 沟通](#1-对-1-沟通) - [信息输入渠道金字塔](#信息输入渠道金字塔) + - [我的信息输入渠道](#我的信息输入渠道) - [目标设定](#目标设定) - [个人愿景的设定](#个人愿景的设定) - [知识内化](#知识内化) @@ -168,6 +172,21 @@ Twitter 里我一般会关注一些 List,比如 [Elixir](https://twitter.com/i 我在学习 [投资](/series/投资实证/) 与 [SEO](/series/seo实践日志/) 领域知识时,基本是通过上述学习流程而执行的。 +#### 我的信息输入渠道 + +以下仅是我常用的信息输入App清单: + +![](https://img.bmpi.dev/b4ac0cc4-b3f5-38a9-f6a8-5cb0554f8fcf.png) + +- 读库:需要订阅,主要是非虚构类的文章。读库主编老六的品味不错,经常能看到一些有意思的人或事的介绍; +- 财新:订阅了财新通,有和读库的组合订阅。可以阅读财新周刊电子版(历年的都可以看),没订阅纸版是因为太贵了,数字版便宜还能在手机上看; +- 端传媒:简体中文新闻媒体能看的很少,繁体中文里
端算较为客观的,能看到一些不同的视角报道; +- 今日热榜:可以订阅一些论坛社区的热门帖子,虽然简体中文新闻媒体很多没法看,但一些好东西可以在老的论坛社区看到。类似的网站有[AnyKnew](https://www.anyknew.com/),还可以用[RSSHub](https://github.com/DIYgod/RSSHub)做自己的基于RSS的聚合阅读器; +- narwhal:是一个我觉得不错的[Reddit](https://reddit.com/)社区客户端,看帖子交互比较好; +- 图书:iOS自带的图书App,配合[Z-Library](https://z-lib.org/)能阅读海量的电子书。可以和macOS的图书自动同步,阅读英文电子书可以用自带的翻译工具大段落的翻译,阅读体验好; +- Octal:[Hacker News](https://news.ycombinator.com/)的第三方App,偶尔看看,平时还是通过邮件阅读每周热门的帖子; +- YouTube:订阅了会员,没有广告观看体验好很多; + ### 目标设定 给自己设定个目标很重要,甚至是个人知识体系最重要的一个环节。如何设定一个长期目标?可以从自身如需求、兴趣、性格、特长、时间等方面考虑,这是一个漫长的过程,我在确定 [我的中长期目标](/goal/) 时也花了很多时间,甚至在确定后不断的微调。 diff --git a/content/self/build-write-tool-v1.zh-cn.md b/content/self/build-write-tool-v1.zh-cn.md index c6d5e9e77..da075cc69 100644 --- a/content/self/build-write-tool-v1.zh-cn.md +++ b/content/self/build-write-tool-v1.zh-cn.md @@ -10,6 +10,9 @@ keywords: ["写作系统", "Hexo", "GitHub Pages", "图床"] description: "本文介绍了使用GitHub Pages以及Hexo工具打造个人优化高效的写作系统" series: ["零成本搭建现代博客指南", "自我提升"] isCJKLanguage: true +categories: [ + "技术写作" +] --- 写作是一件很有趣的事情,长期形成良好的写作习惯可以帮助我们有条理的处理解决问题,同时给人生留下点回忆也不错。在我们很小的时候也许就有记日记的习惯,大脑的记忆量毕竟很有限,所以我们用纸和笔来记东西,但是传统的纸和笔存在很大的问题,无法长久的保存东西。进入互联网时代后我们有了很多选择,各类网站博客、专栏、微博、公众号等可以让我们写作的地方,但是这些都无法解决以下几个问题。 diff --git a/content/self/life-in-plain-text/index.zh-cn.md b/content/self/life-in-plain-text/index.zh-cn.md index 005ca8b98..acaa5c1b5 100644 --- a/content/self/life-in-plain-text/index.zh-cn.md +++ b/content/self/life-in-plain-text/index.zh-cn.md @@ -8,6 +8,9 @@ description: "本文分享我使用纯文本管理我的目标(OKR)、时间(GTD isCJKLanguage: true series: ["自我提升", "人生管理系统"] og_image: "https://img.bmpi.dev/cf3a1023-57ae-b3ce-4110-9624c26f860b.png" +categories: [ + "个人成长" +] --- 我在 [我的时间管理工具](/self/gtd-tools-i-used/) 与 [我的笔记系统](/self/note-system/) 中分享过我不断演进的时间管理工具与笔记系统。在使用这些系统多年后,始终有个困惑萦绕在我耳边,那就是: diff --git a/content/self/my-drawing-toolbox/index.zh-cn.md b/content/self/my-drawing-toolbox/index.zh-cn.md index 1a1389549..d2719012f 100644 --- a/content/self/my-drawing-toolbox/index.zh-cn.md +++ b/content/self/my-drawing-toolbox/index.zh-cn.md @@ -11,6 +11,9 @@ markmap: enabled: true isMermaidEnabled: true plotly: true +categories: [ + "技术写作" +] --- 由于在写作中经常需要配图,本文分享下我常用的绘图工具以及如何存储并展示配图。 diff --git a/content/self/my-writing-story/250k.zh-cn.md b/content/self/my-writing-story/250k.zh-cn.md index ed00f1617..9b729fced 100644 --- a/content/self/my-writing-story/250k.zh-cn.md +++ b/content/self/my-writing-story/250k.zh-cn.md @@ -9,6 +9,9 @@ isCJKLanguage: true og_image: "https://img.bmpi.dev/9208f19f-d1b5-2bfd-a6a8-15d6fa9d9cec.png" markmap: enabled: true +categories: [ + "技术写作" +] --- 不知不觉竟已写了超过 25 万字的文章,实际可能更多。一般的长篇小说都是十几万字以上,所以我竟也在大学毕业后断断续续作文了一部长篇小说的规模。我是怎么做到的? @@ -228,8 +231,6 @@ markmap: 持续的写作最终会驱动作者走向打造个人品牌的路,关于此我之前有做过一个简单的讲座,想了解可看这个Slide:[从内容创作到个人品牌](https://talk.bmpi.dev/2022/content-marketing-to-personal-brand/)。 -以上就是我在写作字数达25万时的一些经验之谈。希望这些文字能帮助你开启自己的写作之路,或者说写作习惯吧。当然这篇文章我也希望在字数达50万甚至100万时能有更多的经验之谈。 - ### 流量分发对创作的影响 总的来说在平台上创作会受到平台流量分发机制的影响。比如在一个类似抖音的平台上创作,流量大多来自平台的自动推荐,这会引导创作者去追逐热点,制作出大量重复的内容,最终胜利者拿走了绝大部分流量。而在一个类似微信公众号订阅制的平台上,流量的分发大多来自创作者的主动推广或用户的口碑推广,少了对流量的争夺,也会让创作者更多元的去创作。 @@ -244,6 +245,8 @@ markmap: 作为一名[长期主义者](/goal/),把时间投入到元能力的提高上,是[自我提升](/series/自我提升/)的一个好的体现。 +以上就是我在写作字数达25万时的一些经验之谈。希望这些文字能帮助你开启自己的写作之路,或者说写作习惯吧。当然这篇文章我也希望在字数达50万甚至100万时能有更多的经验之谈。 + ## 推荐阅读 - [写作 12 年,我的经验和技巧](https://catcoding.me/p/writing-for-joy/) diff --git a/content/self/okr-gtd-note-logseq/index.zh-cn.md b/content/self/okr-gtd-note-logseq/index.zh-cn.md index e3460dfa5..a8a921b16 100644 --- a/content/self/okr-gtd-note-logseq/index.zh-cn.md +++ b/content/self/okr-gtd-note-logseq/index.zh-cn.md @@ -8,6 +8,9 @@ description: "本文分享我使用Logseq实现人生管理系统的OKR、GTD与 isCJKLanguage: true series: ["自我提升", "人生管理系统"] og_image: "https://img.bmpi.dev/d33c93b6-734c-cf47-a3cf-866ccfd29872.png" +categories: [ + "个人成长" +] --- {{< expand "历史更新" >}} diff --git a/content/self/road_to_life_games/index.zh-cn.md b/content/self/road_to_life_games/index.zh-cn.md index 9a864513e..1df625fa6 100644 --- a/content/self/road_to_life_games/index.zh-cn.md +++ b/content/self/road_to_life_games/index.zh-cn.md @@ -10,6 +10,9 @@ keywords: ["时间管理", "目标管理", "资产管理", "技能管理", "社 description: "本文介绍了我对自我提升的理解,分为时间管理、目标管理、资产管理、技能管理、社交管理以及个人品牌的建设" series: ["自我提升"] isCJKLanguage: true +categories: [ + "个人成长" +] --- 前段时间很火的一个美剧《西部世界》,剧中的机器人和正常的人类很相似,其中“老鸨”机器人甚至可以通过修改自己的参数使其变的更为强大。在我们的想象中机器人应该是有很多参数去控制他的行为和“意识”,这好比在游戏中的角色可以通过打怪升级提升自己技能点,而现实世界中一个人也可以通过不断学习与历练提高自己的思维能力和专业技能。 diff --git a/content/talk.zh-cn.md b/content/talk.zh-cn.md index dd3d70a54..b8150527a 100644 --- a/content/talk.zh-cn.md +++ b/content/talk.zh-cn.md @@ -7,10 +7,18 @@ outputs = "html" ## 2022 +### 软件工程师如何打造个人技术影响力 + +- [XConf China 2022](https://app.ma.scrmtech.com/meetings-api/sapIndex/SapSourceData?pf_uid=7019_1254&sid=63242&source=2&pf_type=3&code=061qOR1006W8lO1cFc200VuVl42qOR1q&state=&appid=wx4bd00f95dd7c7ca1) + ### 编程语言是如何实现并发的 - [Slide](https://talk.bmpi.dev/2022/how-to-implement-concurrency/) +### 技术写作的那点事 + +- [直播回放](https://app.ma.scrmtech.com/meetings-api/sapIndex/SapSourceData?pf_uid=7019_1254&sid=54902&source=2&pf_type=3&code=031Pre0006oOmO1qG6000qe8ei0Pre0p&state=&appid=wx4bd00f95dd7c7ca1) + ### 从内容创作到个人品牌 - [Slide](https://talk.bmpi.dev/2022/content-marketing-to-personal-brand/) diff --git a/content/tool.zh-cn.md b/content/tool.zh-cn.md index b483662b0..0313ae503 100644 --- a/content/tool.zh-cn.md +++ b/content/tool.zh-cn.md @@ -12,6 +12,26 @@ outputs = "html" - [Nextjournal - Data-driven Research Livebook](https://nextjournal.com/) - [Elixir Livebook](https://bmpi.fly.dev) - [Metabase - Database Visualization](https://github.com/metabase/metabase) +- [ServerCat - Server Status, Docker Management and SSH client for iOS/macOS](https://servercat.app/) + +## SaaS + +### 云平台 + +- [AWS](https://aws.amazon.com/) +- [Azure](https://azure.microsoft.com/) +- [Vercel](https://vercel.com/) +- [Cloudflare](https://cloudflare.net/) +- [Supabase](https://supabase.com/) +- [Fly.io](https://fly.io/) + +### 网络代理 + +- [Rotating Reverse Proxies](https://stormproxies.com/clients/aff/go/madawei2699) + +## Open Graph Image + +- [Open Graph Image for bmpi.dev](https://og.bmpi.dev/) ## 微信Markdown排版工具 diff --git a/resources/_gen/assets/scss/scss/coder.scss_fd4b5b3f9a48bc0c7f005d2f7a4cc30f.content b/resources/_gen/assets/scss/scss/coder.scss_fd4b5b3f9a48bc0c7f005d2f7a4cc30f.content index 078d3383d..46e8f238a 100644 --- a/resources/_gen/assets/scss/scss/coder.scss_fd4b5b3f9a48bc0c7f005d2f7a4cc30f.content +++ b/resources/_gen/assets/scss/scss/coder.scss_fd4b5b3f9a48bc0c7f005d2f7a4cc30f.content @@ -405,6 +405,31 @@ div.notices.tip p:first-child:after { @media only screen and (max-width: 768px) { *[id^='markmap-about-'] > svg { height: 350px; } } +*[id^='markmap-software-engineering-at-google-culture-3'] > svg { + height: 1000px; } + @media only screen and (max-width: 768px) { + *[id^='markmap-software-engineering-at-google-culture-3'] > svg { + height: 350px; } } +*[id^='markmap-software-engineering-at-google-process-0'] > svg { + height: 1000px; } + @media only screen and (max-width: 768px) { + *[id^='markmap-software-engineering-at-google-process-0'] > svg { + height: 350px; } } +*[id^='markmap-software-engineering-at-google-process-2'] > svg { + height: 1000px; } + @media only screen and (max-width: 768px) { + *[id^='markmap-software-engineering-at-google-process-2'] > svg { + height: 350px; } } +*[id^='markmap-software-engineering-at-google-tool-0'] > svg { + height: 1000px; } + @media only screen and (max-width: 768px) { + *[id^='markmap-software-engineering-at-google-tool-0'] > svg { + height: 350px; } } +*[id^='markmap-authentication-and-authorization-in-a-distributed-system-0'] > svg { + height: 600px; } + @media only screen and (max-width: 768px) { + *[id^='markmap-authentication-and-authorization-in-a-distributed-system-0'] > svg { + height: 350px; } } .mermaid { width: 100%; height: auto; @@ -427,6 +452,21 @@ a[href$="/dev/"] { a[href$="/money/"] { color: #cc5595 !important; } +.share-twitter-container { + text-align: center; + background: -webkit-linear-gradient(45deg, #e5b751, #1677b3, #cc5595 80%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; } + .share-twitter-container a { + display: inline; } + @media only screen and (max-width: 768px) { + .share-twitter-container a { + display: block; } } +.share-twitter-container-dashed { + display: inline; } + @media only screen and (max-width: 768px) { + .share-twitter-container-dashed { + display: none; } } .content { flex: 1; display: flex; diff --git a/static/css/main.css b/static/css/main.css index 10afed6f3..2a900089f 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -547,4 +547,70 @@ li .fa-jk { code.language-ascii { font-family: monospace !important; +} + +/* The Modal (background) */ +.modal { + display: none; /* Hidden by default */ + position: fixed; /* Stay in place */ + z-index: 1; /* Sit on top */ + padding-top: 100px; /* Location of the box */ + left: 0; + top: 0; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + overflow: auto; /* Enable scroll if needed */ + background-color: rgb(0,0,0); /* Fallback color */ + background-color: rgba(0,0,0,0.9); /* Black w/ opacity */ +} + +/* Modal Content (image) */ +.modal-content { + margin: auto; + display: block; + width: 80%; + max-width: 700px; +} + +/* Add Animation */ +.modal-content { + -webkit-animation-name: zoom; + -webkit-animation-duration: 0.6s; + animation-name: zoom; + animation-duration: 0.6s; +} + +@-webkit-keyframes zoom { + from {-webkit-transform:scale(0)} + to {-webkit-transform:scale(1)} +} + +@keyframes zoom { + from {transform:scale(0)} + to {transform:scale(1)} +} + +/* The Close Button */ +.the-modal-close { + position: absolute; + top: 15px; + right: 35px; + color: #f1f1f1; + font-size: 40px; + font-weight: bold; + transition: 0.3s; +} + +.the-modal-close:hover, +.the-modal-close:focus { + color: #bbb; + text-decoration: none; + cursor: pointer; +} + +/* 100% Image Width on Smaller Screens */ +@media only screen and (max-width: 700px){ + .modal-content { + width: 100%; + } } \ No newline at end of file diff --git a/static/js/custom.js b/static/js/custom.js index 2c8089eb8..bfffa7307 100644 --- a/static/js/custom.js +++ b/static/js/custom.js @@ -1,3 +1,10 @@ +// common functions +window.mobileCheck = function() { + let check = false; + (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); + return check; +}; + // load sw.js if("serviceWorker" in navigator) { window.addEventListener("load", () => { @@ -7,6 +14,16 @@ if("serviceWorker" in navigator) { }); } +function fileReader (blob) { + return new Promise((resolve, reject) => { + let reader = new FileReader() + reader.onload = (e) => { + resolve(e.target.result) + } + reader.readAsDataURL(blob) + }) +} + function httpGetAsync(theUrl, callback) { var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function() { @@ -29,6 +46,9 @@ function set_home_page_site_run_days() { } let allPageViewsAPI = "https://api.bmpi.dev/page-views/bmpi-dev-all-page-views/"; +let devPageViewsAPI = "https://api.bmpi.dev/page-views/bmpi-dev-dev-page-views/"; +let selfPageViewsAPI = "https://api.bmpi.dev/page-views/bmpi-dev-self-page-views/"; +let moneyPageViewsAPI = "https://api.bmpi.dev/page-views/bmpi-dev-money-page-views/"; let singlePageViewsAPI = "https://api.bmpi.dev/page-views/bmpi.dev" + window.location.pathname; function get_post_views(url, callback) { @@ -44,6 +64,24 @@ function set_stats_on_home() { get_post_views(singlePageViewsAPI, res => {}); } else { postViews = document.getElementById("post-views"); + devPostViews = document.getElementById("dev-page-views"); + selfPostViews = document.getElementById("self-page-views"); + moneyPostViews = document.getElementById("money-page-views"); + if (devPostViews != undefined) { + get_post_views(devPageViewsAPI, res => { + devPostViews.textContent = JSON.parse(res).count; + }); + } + if (selfPostViews != undefined) { + get_post_views(selfPageViewsAPI, res => { + selfPostViews.textContent = JSON.parse(res).count; + }); + } + if (moneyPostViews != undefined) { + get_post_views(moneyPageViewsAPI, res => { + moneyPostViews.textContent = JSON.parse(res).count; + }); + } if (postViews != undefined) { get_post_views(allPageViewsAPI, res => {}); get_post_views(singlePageViewsAPI, res => { @@ -129,7 +167,7 @@ function addAuthorPart(html) { function addQRPart(html) { let div = document.createElement('div'); let qr = document.createElement('img'); - qr.setAttribute('src', 'https://api.qrserver.com/v1/create-qr-code/?size=70x70&data=' + window.location.href); + qr.setAttribute('src', 'https://api.qrserver.com/v1/create-qr-code/?size=70x70&data=' + window.location.href + '?utm_source=bookmark'); qr.setAttribute('style', 'width: 70px; height: 70px; max-width: none !important;'); div.appendChild(qr); html.appendChild(div); @@ -138,28 +176,55 @@ function addQRPart(html) { async function html2Img(html) { let div = document.createElement('div'); div.id = 'capture'; - div.setAttribute('style', 'padding: 30px 20px;background: #000;color: #f7f4cb;font-family: "LXGW WenKai";'); + div.setAttribute('style', 'padding: 30px 20px;background: #000;color: #f7f4cb;font-family: "LXGW WenKai";max-width: 768px;margin: auto;'); div.innerHTML = html; + let containImages = div.getElementsByTagName('img'); + if (containImages.length > 0 && window.mobileCheck()) { + launch_toast("手机端不支持图片书签,请用电脑浏览器"); + return; + } + for (let i = 0; i < containImages.length; i++) { + let originImg = containImages[i]; + let originImgSrc = originImg.getAttribute('src'); + let imgDataURL = await fetch(originImgSrc) + .then(function (response) { + return response.blob(); + }).then(function (blob) { + return fileReader(blob); + }); + originImg.src = imgDataURL; + } addDatePart(div); let footer = document.createElement('div'); footer.setAttribute('style', 'display: flex;flex-direction: row;justify-content: space-between;align-items: center;margin-top: 20px;padding-top: -20px;padding-top: -20px;border-top-style: dashed;border-top-width: 1px;padding-top: 10px;'); addAuthorPart(footer); addQRPart(footer); div.appendChild(footer); + let bodyBackup = document.body; + let postionBackup = window.pageYOffset || document.documentElement.scrollTop; + if (window.mobileCheck()) { + document.body = document.createElement("body"); + } document.body.appendChild(div); let canvas = await html2canvas(div, {allowTaint: true, useCORS: true}); + if (window.mobileCheck()) { + document.body = bodyBackup; + window.scrollTo(0, postionBackup); + } else { + document.body.removeChild(div); + } let dataURL = canvas.toDataURL("image/png"); - const blob = dataURItoBlob(dataURL); - const data = { - files: [ - new File([blob], document.querySelector('.title').textContent + '.png', { - type: blob.type, - }), - ], - // title: document.title, - // text: text, - }; if (navigator.canShare && navigator.canShare(data)) { + const blob = dataURItoBlob(dataURL); + const data = { + files: [ + new File([blob], document.querySelector('.title').textContent + '.png', { + type: blob.type, + }), + ], + // title: document.title, + // text: text, + }; try { await navigator.share(data); ga('gtag_UA_154678195_1.send', { @@ -183,10 +248,17 @@ async function html2Img(html) { umami.trackEvent('bookmark_fail', 'bookmark'); } } else { - launch_toast("浏览器不支持此功能"); - console.log(`Your system doesn't support sharing files.`); + let modal = document.getElementById("the-modal"); + var modalImg = document.getElementById("the-modal-img01"); + modal.style.display = "block"; + modalImg.src = dataURL; + // Get the element that closes the modal + var span = document.getElementsByClassName("the-modal-close")[0]; + // When the user clicks on (x), close the modal + span.onclick = function() { + modal.style.display = "none"; + } } - document.body.removeChild(div); } async function oncontroldown(event) { @@ -216,16 +288,14 @@ function webShare() { elements.forEach(i => { i.onpointerup = ()=>{ - if (navigator.canShare) { - let selection = document.getSelection(), text = selection.toString(); - if (text !== "") { - let rect = selection.getRangeAt(0).getBoundingClientRect(); - let articleY = document.body.getBoundingClientRect().top; - control.style.top = `calc(${rect.bottom}px - ${articleY}px + 20px)`; - control.style.left = `calc(${rect.left}px + calc(${rect.width}px / 2) - 30px)`; - control['html']= getSelectionHtml(); - document.body.appendChild(control); - } + let selection = document.getSelection(), text = selection.toString(); + if (text !== "") { + let rect = selection.getRangeAt(0).getBoundingClientRect(); + let articleY = document.body.getBoundingClientRect().top; + control.style.top = `calc(${rect.bottom}px - ${articleY}px + 20px)`; + control.style.left = `calc(${rect.left}px + calc(${rect.width}px / 2) - 30px)`; + control['html']= getSelectionHtml(); + document.body.appendChild(control); } } }); @@ -252,12 +322,6 @@ if( document.readyState !== 'loading' ) { // remove dashboard iframe on mobile -// window.mobileCheck = function() { -// let check = false; -// (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); -// return check; -// }; - // var dashboard = document.getElementById("dashboard_iframe"); // if (window.mobileCheck()) { diff --git a/themes/hugo-coder/assets/scss/_base.scss b/themes/hugo-coder/assets/scss/_base.scss index af00bca4a..70c83cf45 100644 --- a/themes/hugo-coder/assets/scss/_base.scss +++ b/themes/hugo-coder/assets/scss/_base.scss @@ -483,6 +483,41 @@ div.notices.tip p:first-child:after { } } +*[id^='markmap-software-engineering-at-google-culture-3'] > svg { + height: 1000px; + @media only screen and (max-width : 768px) { + height: 350px; + } +} + +*[id^='markmap-software-engineering-at-google-process-0'] > svg { + height: 1000px; + @media only screen and (max-width : 768px) { + height: 350px; + } +} + +*[id^='markmap-software-engineering-at-google-process-2'] > svg { + height: 1000px; + @media only screen and (max-width : 768px) { + height: 350px; + } +} + +*[id^='markmap-software-engineering-at-google-tool-0'] > svg { + height: 1000px; + @media only screen and (max-width : 768px) { + height: 350px; + } +} + +*[id^='markmap-authentication-and-authorization-in-a-distributed-system-0'] > svg { + height: 600px; + @media only screen and (max-width : 768px) { + height: 350px; + } +} + .mermaid { width: 100%; height: auto; @@ -509,3 +544,23 @@ a[href$="/dev/"] { a[href$="/money/"] { color: #cc5595 !important; } + +.share-twitter-container { + text-align: center; + background: -webkit-linear-gradient(45deg, #e5b751,#1677b3, #cc5595 80%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + a { + display: inline; + @media only screen and (max-width : 768px) { + display: block; + } + } +} + +.share-twitter-container-dashed { + display: inline; + @media only screen and (max-width : 768px) { + display: none; + } +} diff --git a/themes/hugo-coder/layouts/_default/baseof.html b/themes/hugo-coder/layouts/_default/baseof.html index 6bf40b79c..d6b4a4e04 100644 --- a/themes/hugo-coder/layouts/_default/baseof.html +++ b/themes/hugo-coder/layouts/_default/baseof.html @@ -31,7 +31,7 @@ {{ if .Params.og_image }} {{ else }} - + {{ end }} @@ -39,7 +39,7 @@ {{ if .Params.og_image }} {{ else }} - + {{ end }} {{ partial "schema.html" . }} @@ -132,7 +132,6 @@ {{ if or (hasPrefix .RelPermalink "/dev/") (hasPrefix .RelPermalink "/en/dev/") (hasPrefix .RelPermalink "/self/") (hasPrefix .RelPermalink "/en/self/") (hasPrefix .RelPermalink "/money/") (hasPrefix .RelPermalink "/en/money/") }} - @@ -173,6 +172,11 @@ {{ partial "footer.html" . }} + +