2016年11月份的技术雷达中给出了一个简明的定义:流水线即代码 (Pipeline as Code) 通过编码而非配置持续集成/持续交付 (CI/CD) 运行工具的方式定义部署流水线。 其实早在2015年11月份的技术雷达当中就已经有了类似的概念:
The way to avoid programming in your CI/CD tool is to extract the complexities of the build process from the guts of the tool and into a simple script which can be invoked by a single command. This script can then be executed on any developer workstation and therefore eliminates the privileged/singular status of the build environment 大意是将复杂的构建流程纳入一个简单的脚本文件,然后用一条命令调用。这样,任意的开发者都能在自己的工作区中执行脚本重建一套一模一样的构建环境,从而消除 CI/CD 环境由于散乱配置腐化而成的特异性。这么做的原因很好理解,使用 CI/CD 工具是为了暴露产品代码中的问题的,如果它们自身已经复杂到不稳定的地步,我们还使用它就是自找麻烦。
从某种程度上看,实施流水线即代码是不证自明的。在 CI/CD 的时间过程中,凡是可以被编码的东西都已经被代码化了,比如:构建、测试、数据库迁移、部署和基础设施/环境配置 (Infrastruture as Code) 等。说得烂俗点,流水线已经是 CI/CD 实践过程中的“最后一公里”,让流水线变成软件开发中的“一等公民”(即代码)是大势所趋、民心所向。不过,这种论断毕竟欠缺说服力,我们接着从实践的痛点出发总结当前流水线遇到的问题。
这些问题会在流水线的演化过程中恶化得越来越严重。一般来讲,除非不再使用,否则流水线不会保持一成不变。具体实施过程中,考虑到项目,尤其是遗留项目当前的特点和团队成员的“产能”,我们会先将构建和部署自动化;部署节奏稳定后,开始将单元测试和代码分析自动化;接着可以指导测试人员将验收测试自动化;然后尝试将发布自动化。在这之后,就要开始持续优化流水线,包括 CI 的速度和稳定性等。换句话说,流水线的演化其实是和项目的当前进展密切相关的,保证这样的对应关系有时是有必要的,比如:在版本控制下,多发布分支所需流水线和主干分支会存在不同。发布分支是主干分支某个时刻分出去的,它需要在那时的流水线上才能正常工作。由于前面所说雪花服务器的特征,重建这样一条流水线并不是一件容易的事情。
话说回来,为什么我会从vim党摇身一变成为emacs党呢?这就不得不提起Clojure这门lisp方言,出于对lisp和函数式编程的痴迷,我选择了基于JVM的Clojure作为自己的偏好语言,而emacs天生为lisp而生。有了这个充足的理由,我开始收集emacs的cheatsheet并打印出来,天天放在手边翻阅,甚至买了一本英文版的Learning GNU Emacs书籍,只要有机会就打开emacs开始刷4clojure上的编程题。由于emacs对lisp的亲和性,我几乎没花多少时间就掌握住了常用的操作技巧。不过,emacs最负盛名的学习曲线确实让学习者绕过圈子,只要一段时间不用,就会忘记很多基本操作。另外,为了更好地在emacs中编写Clojure,还需要cider-mode和clojure-mode的支持,这时候就不得不编辑init.el文件,本着KISS (keep it simple, stupid)原则,我照着各种插件的说明文档中,把配置项复制粘贴到init.el文件当中,运行起来没有问题就好。随着自定义的内容变多,init.el文件也急剧膨胀起来。膨胀本来算不上问题,但我是个比较有操守的程序员,臃肿的代码是我极力避免的坏味道(bad smell)。所以胸臆之中涌动一股浩然之气,决心学起emacs lisp,把emacs的配置从头来过。
开始我觉得这是一对矛盾的配置,package-enable-at-startup设置为nil,暗示emacs启动时不会启用package,而package-initialize明显表明在做package的初始化工作。这种时候,我心中就蹦跶出一句话“世界上本没有矛盾,如果出现了,检查你都有哪些前提条件,就会发现其中一个是错的”。这种非异常的知识点很难通过搜索引擎找到满意的答案,而阅读文档恰恰是最合适的解决方式。emacs对elisp文档的支持非常全面,只需将鼠标移到package-enable-at-startup变量上,按下c-h v (control + h, v) 组合键,就能在其它窗口(window) 看到文档描述:
Whether to activate installed packages when Emacs starts. If non-nil, packages are activated after reading the init file and before after-init-hook'. Activation is not done if user-init-file’ is nil (e.g. Emacs was started with “-q”).
Like ‘progn’, but evaluates the body at compile time if you’re compiling. Thus, the result of the body appears to the compiler as a quoted constant. In interpreted code, this is entirely equivalent to `progn’.
If you’re using another package, but only need macros from it (the byte compiler will expand those), then ‘eval-when-compile’ can be used to load it for compiling, but not executing. For example,
1 2
(eval-when-compile (require 'my-macro-package))
这里头有三个关键字load、compiling和executing值得留意一下。为了弄懂它们的含义,我们需要了解lisp解析器基本的工作原理:code text -[characters]-> load -[lisp object]-> evaluation/compiling -[bytecode]-> lisp interpretor。换句话说,除非你想编译包含上述代码的文件,否则它的作用和progn一模一样,顺序地求值包含其中的表达式。当你正在编译文件的时候,包中宏就会原地展开,然后被eval-when-compile宏加载进内存并被编译成字节码,供后续解析器执行。
第一点在于测试的意图。用例太过具体,我们就很容易忽略自己的测试意图。比如我曾经看过有人在写计算器kata程序的时候,将其中的一个测试命名为return 3 when add 1 and 2,这样的命名其实掩盖了测试用例背后的真实意图——传入两个整型参数,调用add方法之后得到的结果应该是参数之和。我们常说测试即文档,既然是文档就应该明确描述待测方法的行为,而不是陈述一个例子。
首先,我们尝试声明两个参数可能出现的情况或者称为规格(specification),即参数a和b都是整数。然后调用生成器产生一对整数。整个分析和构造的过程中,都没有涉及具体的数据,这样会强制我们揣摩输入数据可能的模样,而且也能避免测试意图被掩盖掉——正如前面所说,return 3 when add 1 and 2并不代表什么,return the sum of two integers才具有普遍意义。
(doc juxt) -> clojure.core/juxt [f] [f g] [f g h] [f g h & fs] Added in 1.1 Takes a set of functions and returns a fn that is the juxtaposition of those fns. The returned fn takes a variable number of args, and returns a vector containing the result of applying each fn to the args (left-to-right). ((juxt a b c) x) => [(a x) (b x) (c x)]
(defroom-2 {:room-id2 :periods [{:time"17:00-18:00" :status:occupied} {:time"18:00-19:00" :status:available}]}) (facts"about `the-earliest-avaible-period-of-bathroom`" (fact"should recommand if there is only one room with available period" ;; 1号 (the-earliest-available-recommand [room-1]) => {:room-id1:time"17:00-18:00"}) ;; 2号 (the-earliest-available-recommand [room-2]) => {:room-id2:time"18:00-19:00"}))
(defroom-3 {:room-id3 :periods [{:time"17:00-18:00" :status:occupied} {:time"18:00-19:00" :status:available} {:time"19:00-20:00" :status:available}]}) ... (fact"should recommand the earliest one if there is only one room with multiple available periods" (the-earliest-available-recommand [room-3]) => {:room-id3:time"18:00-19:00"})
ga . gcmsg "one room with multiple available periods"
第3个任务
所有澡堂(包含输入为空)没有可用时间段
1. 写测试
1 2 3 4 5 6 7 8 9 10 11
(defnon-available-room {:room-id4 :periods [{:time"17:00-18:00" :status:occupied} {:time"18:00-19:00" :status:occupied} {:time"19:00-20:00" :status:occupied}]}) (fact"should show `:no-available-room` if there is no available room" (the-earliest-available-recommand []) => :no-available-room (the-earliest-available-recommand [non-available-room]) => :no-available-room))
(fact"should recommand the earliest if there has more than one room and each has available periods" (the-earliest-available-recommand [room-1 room-2]) => {:room-id1:time"17:00-18:00"} (the-earliest-available-recommand [room-2 room-1]) => {:room-id1:time"17:00-18:00"} (the-earliest-available-recommand [room-2 room-3]) => {:room-id2:time"18:00-19:00"} (the-earliest-available-recommand [room-1 room-2 room-3]) => {:room-id1:time"17:00-18:00"})
ga . gcmsg "[refactor] use juxt to extract needed fields"
第5个任务
多间澡堂中有的有可用时间段,有的没有可用时间段
1. 写测试
1 2 3
(fact"should recommand the earliest available room even if there has non available room" (the-earliest-available-recommand [room-1 non-available-room]) => {:room-id1:time"17:00-18:00"} (the-earliest-available-recommand [room-2 non-available-room]) => {:room-id2:time"18:00-19:00"})
ga . gcmsg "mixed non-available and available rooms"
为第3个任务补上测试用例
所有(包含多个)澡堂(包含输入为空)没有可用时间段
1 2 3 4
(fact"should show `:no-available-room` if there is no available room" (the-earliest-available-recommand []) => :no-available-room (the-earliest-available-recommand [non-available-room]) => :no-available-room (the-earliest-available-recommand [non-available-room non-available-room]) => :no-available-room))
这里的第3个用例包含第2个用例,我们待会整理掉。不过现在先提交一下。
1 2
ga . gcmsg "multiple non-available rooms"
整理测试
在前面进行的任务当中,我们发现有两次没有写实现测试就通过的情况。这说明测试用例是有覆盖的。
第2个任务的测试用例其实覆盖了第1个任务的测试用例,所以可以直接删去后者;
第5个任务的测试用例覆盖了第4个任务的部分测试用例,所以可以合并到一起。
整理下来,最终的测试变成下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
(facts"about `the-earliest-avaible-period-of-bathroom`" (fact"should recommand the earliest one if there is only one room with multiple available periods" (the-earliest-available-recommand [room-3]) => {:room-id3:time"18:00-19:00"}) (fact"should show `:no-available-room` if there is no available room" (the-earliest-available-recommand []) => :no-available-room (the-earliest-available-recommand [non-available-room non-available-room]) => :no-available-room) (fact"should recommand the earliest if there has more than one room and each may have available periods" (the-earliest-available-recommand [room-1 room-2]) => {:room-id1:time"17:00-18:00"} (the-earliest-available-recommand [room-2 room-1]) => {:room-id1:time"17:00-18:00"} (the-earliest-available-recommand [room-2 room-3]) => {:room-id2:time"18:00-19:00"} (the-earliest-available-recommand [room-1 room-2 room-3]) => {:room-id1:time"17:00-18:00"} (the-earliest-available-recommand [room-1 non-available-room]) => {:room-id1:time"17:00-18:00"}))
文档
The final goal of any engineering activity is some type of documentation.
(defnsum-number-pairs [input-file output-file] "Read the data from input-file, which contains two floats per line separated by a space. Open file named output-file and, for each line in input-file, write a line to the output file that contains the two floats from the corresponding line of input-file plus a space and the sum of the two floats." (with-open [rdr (io/reader input-file) wtr (io/writer output-file :appendtrue)] (loop [line (.readLine rdr)] (when line (let [pair (map read-string (str/split line #"\s")) first (first pair) second (second pair) sum (+ first second)] (.write wtr (str first " " second " " sum "\n"))) (recur (.readLine rdr))))))
COMPND AMMONIA ATOM 1 N 0.257 -0.363 0.000 ATOM 2 H 0.257 0.727 0.000 ATOM 3 H 0.771 -0.727 0.890 ATOM 4 H 0.771 -0.727 -0.890 END COMPND METHANOL ATOM 1 C -0.748 -0.015 0.024 ATOM 2 O 0.558 0.420 -0.278 ATOM 3 H -1.293 -0.202 -0.901 ATOM 4 H -1.263 0.754 0.600 ATOM 5 H -0.699 -0.934 0.609 ATOM 6 H 0.716 1.404 0.137 END
第一行描述的是分子的名字,接下来到END为止的每行代表原子的ID、类型以及在分子中分布的[x y z]坐标。 我们需要一个函数,将数据读取出来并且以规定的格式输出,格式如下:
COMPND AMMONIA ATOM 1 N 0.257-0.3630.000 ATOM 2 H 0.2570.7270.000 ATOM 3 H 0.771-0.7270.890 ATOM 4 H 0.771-0.727-0.890 COMPND METHANOL ATOM 1 C -0.748-0.0150.024 ATOM 2 O 0.5580.420-0.278 ATOM 3 H -1.293-0.202-0.901 ATOM 4 H -1.2630.7540.600 ATOM 5 H -0.699-0.9340.609 ATOM 6 H 0.7161.4040.137
clojure.core/line-seq [rdr] Added in 1.0 Returns the lines of text from rdr as a lazy sequence of strings. rdr must implement java.io.BufferedReader.
可以确认line-seq返回一个惰性的字符串序列。 再看看(doc take)的文档,得到
1 2 3 4 5 6 7
clojure.core/take [n] [n coll] Added in 1.0 Returns a lazy sequence of the first n items in coll, or all items if there are fewer than n. Returns a stateful transducer when no collection is provided.
插件确实不好写,因为插件是插入庞大的系统当中工作的,那也就意味着写插件需要具备一定的领域知识,包括系统架构、扩展点、业务共性及差异、API及其业务模型对应、安装和测试。而对于开发者而言,学习这些知识的代价绝对是昂贵的。 在《函数式编程思想》一书中,作者Neal Ford提到开发过程当中的两种抽象方式——composable and contextual abstract. 谈及contextual抽象的时候,他把插件系统列为这一抽象中最经典的例子。
Plugin-based architectures are excellent examples of the contextual abstraction. The plug-in API provides a plethora of data structures and other useful context that developers inherit from or summon via already existing methods. But to use the API, a developer must understand what that context provides, and that understanding is sometimes expensive.
Scanner, which runs the source code analysis Compute Engine, which consolidates the output of scanners, for example by computing 2nd-level measures such as ratings aggregating measures (for example number of lines of code of project = sum of lines of code of all files) assigning new issues to developers persisting everything in data stores Web application
PMD is a source code analyzer. It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, and so forth. It supports Java, JavaScript, PLSQL, Apache Velocity, XML, XSL.
<description> Example set of configured PMD rules </description>
<rulename="ComponentsMustNotBeFollowedByComponentsRule" message="Components tags followed by components tag found!" language="xml" class="net.sourceforge.pmd.lang.rule.XPathRule">
<description> Tag components must not be followed by components tag. </description>
privatevoidconvertToIssues(SensorContext context, Document doc) { finalElementroot= doc.getRootElement(); final List<Element> files = root.elements("file"); for (Element file : files) {
final List<Element> violations = file.elements("violation"); finalStringfilePath= file.attributeValue("name"); finalFileSystemfs= context.fileSystem(); finalInputFileinputFile= fs.inputFile(fs.predicates().hasAbsolutePath(filePath)); if (inputFile == null) { LOG.info("fs predicates that there is no {}", filePath); continue; } for (Element violation : violations) { finalStringrule= violation.attributeValue("rule"); finalintbeginLine= Integer.parseInt(violation.attributeValue("beginline")); finalintendLine= Integer.parseInt(violation.attributeValue("endline")); finalintbeginColumn= Integer.parseInt(violation.attributeValue("begincolumn")); finalintendColumn= Integer.parseInt(violation.attributeValue("endcolumn")); finalNewIssuenewIssue= context.newIssue() .forRule(RuleKey.of(CustomRulesDefinition.REPOSITORY_KEY, rule)); finalNewIssueLocationnewIssueLocation= newIssue .newLocation() .on(inputFile) .at(inputFile.newRange(beginLine, beginColumn, endLine, endColumn)) .message(violation.getText()); newIssue.at(newIssueLocation).save(); } } }
这里主要是对PMD生成XML报告的解析和转换。比较需要关注是这块代码:
1 2 3 4 5
finalInputFileinputFile= fs.inputFile(fs.predicates().hasAbsolutePath(filePath)); if (inputFile == null) { LOG.info("fs predicates that there is no {}", filePath); continue; }
$ javac App.java $ javap -v App.class |grep major major version: 52
如果指定source和target参数,再用javac编译App.java
1 2 3 4 5 6 7 8
$ java -version java version "1.8.0_45" ... $ javac -source 7 -target 7 App.java warning: [options] bootstrap class path not set in conjunction with -source 1.7 1 warning $ ls App.class App.java
目前为止还没有任何问题。但若是运行时,这段程序很可能抛出异常java.io.FileNotFoundException: your-file-name (No such file or directory)。原因在于file的路径当中可能存在多级父级目录,例如:outputDir/p1/p2/srcX.html,那么当FileWriter尝试创建srcX.html就会失败。此时最简单的方法就是提前创建好所有的父级目录,于是*outputPath()*方法会变成下面这样:
public boolean mkdirs() Creates the directory named by this abstract pathname, including any necessary but nonexistent parent directories. Note that if this operation fails it may have succeeded in creating some of the necessary parent directories. Returns: true if and only if the directory was created, along with all necessary parent directories; false otherwise
With the throws type parameter on the Block interface, we can now accurately generify over the set of exceptions thrown by the Block; with the generic forEach method, we can mirror the exception behavior of the block in forEach(). This is called exception transparency because now the exception behavior of forEach can match the exception behavior of its block argument. Exception transparency simplifies the construction of library classes that implement idioms like internal iteration of data structures, because it is common that methods that accept function-valued arguments will invoke those functions, meaning that the library method will throw a superset of the exceptions thrown by its function-valued arguments.
1. Download nvm-noinstall.zip 2. Update the system environment variables: NVM_HOME, NVM_SYMLINK (C:\Users\Program Files\nodejs This directory should not exist in previously.) 3. Create settings.txt file
var bin = new Buffer('hello', 'utf8');// <Buffer 68 65 6c 6c 6f> bin.toString(); //'hello' var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]); bin.toString(); //'hello'
var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]); var dump = new Buffer(bin.length); bin.copy(dump);
wrapSetTimeout(() => {x.func()}, (err, data) => { if(err) console.log("I catch you again", err); }) =>I catch you again [ReferenceError: x is not defined