Bootstrap

ARTS 04 - 使用 Gitlab + Generic Webhook Trigger 触发 Jenkins 自动化构建

提出来的一个打卡任务。每周一个 Algorithm,Review 一篇英文文章,总结一个工作中的技术 Tip,以及 Share 一个传递价值观的东西!我希望这个事可以给大家得到相应的算法、代码、技术和影响力的训练。

这是我的第四周打卡。标题为”使用 Gitlab + Generic Webhook Trigger 触发 Jenkins 自动化构建”。这周主要想分享的是使用 Jenkins 构建 CI/CD 流程中的一些心得。

Algorithm

描述:

题解:

思路一:哈希表

前面在做的问题时,使用到了哈希表的解法。对于这道题,同样地我们可以把访问过的节点存在哈希表里面,通过检查节点之前是否访问过来判断是否是环形链表。

/**
 * 判断链表是否有环
 * @param {ListNode} head 
 * @return {ListNode}
 */
export function hasCycle(head) {
  const map = new Map()
  while (head) {
    if (map.has(head)) {
      return true
    }
    map.set(head, true)
    head = head.next
  }
  return false
}

时间复杂度:O(n)

空间复杂度:O(n)

思路二:双指针快慢指针

在解决链表的问题时,感觉用指针才是最标准的解法,可以训练大家对链表这种结构的算法思维。这道题可以想象成在一个环形的跑道上速度不同的跑步者相遇的问题。这里用了两个指针,一个快指针,每次走两步;一个慢指针,每次走一步。如果是环形链表,最终快指针会和慢指针相遇。如果想验证这种思路是否是正确的,可以在纸上画一下,分奇偶链表两种情况。

/**
 * 判断链表是否有环,双指针快慢指针解法
 * @param {ListNode} head 
 * @return {ListNode}
 */
export function hasCycle(head) {
  let slowPos = head, fastPos = head
  while (fastPos) {
    if (fastPos.next == null) {
      return false
    }
    slowPos = slowPos.next
    fastPos = fastPos.next.next
    if (slowPos == fastPos) {
      return true
    }
  }
  return false
}

时间复杂度:O(n)

空间复杂度:O(1)

PS: 自己用 ES6 实现了下环形链表的数据结构

//声明一个链表结点
export class ListNode {
  constructor(val) {
    this.val = val
    this.next = null
  }
}

/**
 * 数组转链表
 * @param {Array} arr 
 * @return {ListNode} 
 */
export function array2List(arr) {
  if (arr.length === 0) {
    return null
  }
  const head = new ListNode(null);
  let i = 0, curr = head
  for (i; i < arr.length; i++) {
    curr.next = new ListNode(arr[i])
    curr = curr.next
  }
  return head.next
}

/**
 * 
 * 环形链表实现
 * head = [3,2,0,-4], pos = 1
 * 3->2->0->-4->2->0->-4->2...
 * @param {Array} arr 
 * @param {Integer} index 
 * @return {ListNode}
 */
export function cycleList(arr, index) {
  const linkedList = array2List(arr)
  let i = 1, circle, p = linkedList
  while (p.next) {
    if (i == index + 1) {
      circle = p
    }
    p = p.next
    ++i
  }
  p.next = circle
  return linkedList
}

Review

这篇文件介绍的是如何在 Elixir 的守卫语句中使用自定义函数的思路。默认情况下,在 Elixir 的守卫语句中是不允许使用自定义函数的,如果这么做,会报一个如下的错误。

> elixir guards.exs
** (CompileError) guards.exs:18: cannot invoke local kid?/1 inside guard
    guards.exs:18: (module)

作者解释了一下原因。Erlang 的创建者为了保证守卫语句的快速以及纯度,所以限制了守卫语句只能使用内建的函数。因为他没法确定用户自定的函数是否是快速的纯函数。

但是,这并不意味着我们没有选择的余地。我们可以把自定义的函数定义为宏来实现,这主要得益于 Elixir的元编程。

最后的结论是,虽然可以这样实现,但是我们需要考虑为什么会有这种局限性。作者在设计语言的时候,会去权衡利弊,编程的很大一部分是权衡。所以一旦我们考虑了问题的局限性,就更容易理解作者为什么要做出他们的权衡。

Tip

Elixir return eraly

这周在写 的代码时,发现没有 关键字。之前使用的语言,包括 、、、 都是有 关键字的。

在写代码时,我们都习惯先去判断错误的分支,然后 结束掉。在 中没有 关键字怎么办,我们可以通过使用其它的一些手段来提前结束掉代码。这些手段如下:

一开始很不习惯,有点不明白作者设计的意图是为什么。尝试一段时间后,发现代码的整洁度比之前的 写法好多了,可以举个例子说明一下:

比如我们需要对一个业务做很多检查,如果使用 的写法,代码可能是这样的:

# 检查格式
if(!check_form_validation()){
    ...
    return
}
...
# 业务检查
if(!check_business1()){
    ...
    return
}
...
if(!check_business2()){
    ...
    return
}
...
if(...){
    return
}
...

甚至有些情况我们可能连 、 这些检查的逻辑都直接写在方法体里,没有抽成一个方法。

随着业务不断增加,到时候一个方法里面错误分支的代码越来越多,happy case 可能就是一两句代码。这样导致的问题是,一方面助长程序员去写意大利面条式代码;另一方面,方法严重违背了单一职责的原则。

没有 后,我们就需要考虑怎么去组织代码的结构,整个代码的结构可能是下面的样子:

 with :ok <- check_form_validation(),
         :ok <- check_business1(),
         :ok <- check_business2() do
      # happy case 业务逻辑
      {:ok, %{file_url: full_path, file_name: file_name}}
    else
      # 错误分逻辑    
      {:error, msg} -> {:error, msg}

整个代码的逻辑变得十分清晰,如果以上条件都满足后需要干什么;当运行到某个函数时不满足就会走到错误分支。另一个好处是保证了我们函数返回的数据结构都是一致的。

另一种写法是哨兵子句,主要是针对某个具体的函数的。比如我们拿上传文件来举个例子,我们要检查文件的格式、大小等等。我们可以这样来写:

def upload_file(%{"file" => file} = params) when is_nil(file), do: "File can not empty"

def upload_file(%{"file" => file} = params) when file.size > 1000, do: "File size exceed"

def upload_file(%{"file" => file} = params) do
#上传文件
end

Share

分享文章:

这周在用 Jenkins 持续优化项目的 CI/CD 流程时,总结的一些心得。在 Jenkins 的使用过程中,我发现实践很重要,很多工具、比较好的实践都需要自己去查找一些资料,然后真正地去项目中实践才能掌握。