生産性向上ブログ

継続的な生産性向上を目指すエンジニアのためのブログ

『レガシーソフトウェア改善ガイド』読了

レガシーソフトウェア改善ガイド

レガシーソフトウェア改善ガイド

『レガシーソフトウェア改善ガイド』を読み終えたので、簡単に感想とかまとめておきます。

タイトルから、『レガシーコード改善ガイド』を連想されると思いますが、特に続編ではなく、そもそも作者が違います。良し悪しはさておき、まぎらわしいことは間違いないです。

『レガシーコード改善ガイド』では、「テストのないコードはレガシーコード」という定義だったのに対して、『レガシーソフトウェア改善ガイド』では、「保守または拡張が困難」なものであればなんでもレガシーと定義しています。そして、コードだけではなく、それ以外のプロジェクト全体についてもかなりの量を割いて記述しています。本が扱う内容は、おおまかに以下のようなものがあります。

  • レガシープロジェクトの定義、性質、なぜレガシーになるのか
  • 改善のための指標、その計測方法
  • チームとのコミュニケーション
  • リファクタリングとレガシーコードのテスト
  • リアーキテクチャリング、リライト
  • 開発環境構築やデプロイの自動化

正直、1冊の本によくここまで詰め込んだなという幅広い内容です。

1つのトピックに対して深掘りする本ではないですが、そこまで多くない分量の中で実戦的かつ本質的な部分がよくまとまっていると思います。自分含め、世の中の開発者がレガシーコードに対して感じていることをうまく言語化してくれていると感じました。新しい知識が得られる本ではないかもしれませんが、レガシープロジェクトと戦うための引き出しを一通り確認したいときや、チームでレガシーな部分にどのように立ち向かっていくか認識を揃えるときにとても役に立つ本なのではないでしょうか。

個人的に取り入れたいなと思ったのは、個々の変更レベルでのレビューだけではなく、定期的にコードベース全体のレビューをチームで行うという部分です。普段放置されがちな部分は、いざというときになってから問題が顕在化しがちです。また、全体としての設計やコード品質の一貫性という部分は意識的に改善しないと、実装当初は問題なくても、ゆるやかにレガシー化していくでしょう。なにより、定期的な習慣として改善の姿勢を明確化することは、組織の文化づくりとしてとても良さそうに思いました。

エンジニアは、多かれ少なかれレガシーなプロジェクトに関わるものです。それどころか、大半のエンジニアは新規プロジェクトよりもレガシー化した後のプロジェクトに関わる時間の方が圧倒的に長いでしょう。そう考えるとこの本は、エンジニア人生を豊かにしてくれる一冊と言えます。

Jenkins 2.0 (5): Jenkinsfileをコマンドラインからlintする

Jenkins 2.0のDeclarative Pipelineには、Jenkinsfileをlintするための仕組みが用意されています。

curlとssh

プラグインのWikiには、sshとcurlそれぞれでJenkinsをlintするための方法が紹介されています。以下は引用です。

# ssh方式
ssh -p [sshd port on master] [Jenkins master hostname] declarative-linter < Jenkinsfile

# curl方式
curl -X POST -H `curl 'JENKINS_URL/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)'` -F "jenkinsfile=<Jenkinsfile" JENKINS_URL/pipeline-model-converter/validate

ssh方式の場合は、「Jenkinsの管理」→「システム設定」→「SSHサーバー」からsshdポートを指定しておくのがいいでしょう。

curl方式の -H `curl 'JENKINS_URL/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)'` のサブコマンド部分は、「グローバルセキュリティの設定」→「CSRF対策」→「Default Crumb Issuer」が有効になっている場合のみ必要です。Jenkins 2.0ではデフォルトで有効になっているはずです。さらに、ログイン前ユーザーにread権限がない場合は、 -u <username>:<password> を追加するなどして認証情報を与えないと 403 Forbidden でエラーになってしまいます。

jflint

上記の方式だと、日常的に叩くにはちょっとコマンドが複雑です。なので、jflintというコマンドラインツールをnpmパッケージとして作成しました。

$ npm install -g jflint

でインストールできます。

$ jflint -j [Jenkins master hostname] Jenkinsfile

でlintが走ります。内部的には、curl方式でJenkinsにリクエストを投げているだけですが、curlコマンドを直に叩くよりは楽だと思います。

さらに、 -j [Jenkins master hostname] 部分も毎回書くのが面倒なので、設定ファイルから自動で読み込めるようにしています。

{
  "jenkinsUrl": "[Jenkins master hostname]"
}

のようなjsonを、 .jflintrc という名前のファイルとして、現在のディレクトリからファイルシステムルートまでの間か、ホームディレクトリに保存してください。すると、

$ jflint Jenkinsfile

だけでlintできるようになります。

まとめ

JenkinsfileをCLI上からlintする方法と、入力を簡単にするためのnpmパッケージ、jflintについて紹介しました。Jenkinsfileをいじるときに手軽にlintできるの便利なので、ぜひ使ってみてください。なにか不具合や質問などあれば、issue立てるなり、なんらかの手段で自分に連絡するなりしていただけると助かります。

Jenkins 2.0 (4): GitHub Organization Folder

Jenkins 2.0について書く記事の4回目です。今回は、GitHub連携について書きます。

GitHub連携でやりたいこと

昨今の一般的なCIサービスでは、GitHub連携に求められることは以下になります。

  • リポジトリにpushしたら自動でビルド開始される
  • ビルド結果をcommit statusへ反映

ちなみに、commit statusとは↓のようなGitHub上でコミットの状態を可視化してくれるすばらしいものです。

f:id:miya-jan:20170310083446p:plain

1.0時代のGitHub連携

過去のJenkinsのフリースタイルジョブで上記のようなGitHub連携を実現しようと思うと、GitHub Pluginをインストールした上で次のような設定が必要でした。

  • 「Jenkinsの管理」→「システムの設定」→「GitHub」→「GitHub Servers」で「Add GitHub Server」
    • 事前にGitHub側で “repo:status” を許可したPersonal access tokenを作成しておく
    • ↑のトークンをCredentialsに “Secret text” として追加して設定する
  • 「新規ジョブ作成」→「フリースタイル・プロジェクトのビルド」でジョブ作成
  • 「ソースコード管理」で「Git」を選択
    • 「Repository URL」を入力
    • 「Branches to build」を「**」とかにする
  • 「ビルド・トリガ」を設定
    • ネットワーク的にGitHubからJenkinsへのWebhookリクエストが届くなら「GitHub hook trigger for GITScm polling」をチェック
    • 届かないなら「SCMをポーリング」をチェック
  • ビルド手順に、「Set build status to “pending” on GitHub commit」を追加
  • 「ビルド後の処理」で「Set GitHub Commit Status」を設定
    • 「Status result」に「One of default messages and statuses」を設定

な、長い。。

さらに辛いのは、最初のシステム設定以外はジョブを新しく作成するごとに設定する必要があるということです。これでは誰でも気軽にGitHub連携したジョブを作成できるとは言い難く、一般的にJenkins職人と呼ばれる人が誕生してしまうのもしょうがないでしょう。

GitHub Organization Folder

現在のJenkinsでは、 GitHub Organization Folder を使うことによってGitHub連携が圧倒的に楽になります。Organization Folderは、Organization内にあるリポジトリをスキャンして、リポジトリのルートにJenkinsfileがあるリポジトリに対してMultibranch Pipelineジョブ(後述)を自動で作成してくれます。

GitHub Organization Folderは、2.0でデフォルトでインストールされるGitHub Branch Source Pluginによって提供される機能です。以前は、GitHub Organization Folder Pluginの機能として提供されていましたが、GitHub Branch Source Pluginに機能が移譲されました。

新規ジョブ作成画面で、以下のように「GitHub Organization」という選択肢が表示されるようになります。

f:id:miya-jan:20170311142816p:plain

  • 事前にGitHub側で “repo:status” を許可したPersonal access tokenを作成しておく
  • 「新規ジョブ作成」→「GitHub Organization」でGitHub Organization Folderを作成
    • 「Owner」にGitHubのOrganization名かユーザーIDを指定
    • 「Scan credentials」に「ユーザー名とパスワード」としてGitHubのユーザーIDとPersonal access tokenの組み合わせを追加して設定する
    • ネットワーク的にGitHubからJenkinsへのWebhookリクエストが届かないなら、「Scan Organization Triggers」の間隔をポーリングしたい間隔に設定しておく
  • 自動ビルドしたいリポジトリのルートにJenkinsfileを追加する

だいぶ簡単になりましたね。

重要なのは、GitHub Organization Folderは一度設定してしまえば、同じOrganization内のリポジトリで新規ジョブを作成するときはJenkinsfileを作成するだけでいいということです。これは、チーム開発をする上でJenkinsおじさんの必要性を大きく下げてくれます。

一点注意が必要なのは、現時点ではOwnerにOrganizationではなくユーザーIDを指定したときはWebhookの登録が自動でされないので手動で行う必要があります。

Multibranch Pipeline

Organization Folderで作成されるMultibranch Pipelineジョブについて説明します。

Multibranch Pipelineはリポジトリ内のすべてのブランチに対して、push時の自動ビルドやビルド結果のcommit statusへの反映といったことを行います。また、リポジトリ内のブランチごとにビルドを分類してくれます。要は、CircleCIとかTravisとかと同じですね。

f:id:miya-jan:20170320124915p:plain

Multibranch Pipelineジョブは、Organization Folder以外でも、個別のリポジトリに対しても作成できます。「新規ジョブの作成」から「Multibranch Pipeline」を選択することで作成可能です。

ただし、こちらの方法で作成すると現時点ではGitHub側へのWebhookの登録を行ってくれないようなので、手動で “http://(Jenkins URL)/github-webhook/” へWebhookを設定する必要があります。

GitHubのpersonal access tokenのscopeについて

Jenkinsのcredentialに設定するGitHubのpersonal access tokenですが、なにをするためにどのscopeが必要なのかわかりにくいので簡単にまとめておきます。

  • repo : privateリポジトリにアクセスするために必要
  • repo:status : commit statusを変更するために必要
  • admin:repo_hook : RepositoryにWebhookを設定するために必要
  • admin:org_hook : OrganizationにWebhookを設定するために必要

まとめ

Jenkins 2.0では、GitHub Organization FolderのおかげでGitHub連携がとても楽になりました。ちなみに、BitBucketにも対応しているようです。

まだ最初の設定まわりでいくらかはまりどころもありそうに感じますが、一度設定すればそのOrganization内では再設定の必要がなくなるというのはとても嬉しいです。

Jenkins 2.0 (3): Scripted Pipeline と Declarative Pipeline

Jenkins 2.0について書く記事の3回目です。今回は、前回書いたJenkins Pipelineを構成するDSLについて書きます。

フリースタイルプロジェクトのジョブ設定

1.x時代から存在したフリースタイルプロジェクトでは、Web上のUIを通してジョブを設定しました。この方法は、前回書いたように、わかりやすい反面いくつか問題点もありました。

  • 機能が増えるにつれ設定UIが複雑になりがち
  • 1つのジョブにつき1つのノードしか使えない
  • デプロイパイプラインを構築するためには複数ジョブをつなげないといけない
  • 設定の変更履歴を管理しづらい

Scripted Pipeline

2.0のPipelineでは、GroovyによるDSLが導入され、Pipeline as Codeとしてジョブを設定できるようになりました。DSLによる柔軟な表現力により、上記の問題が改善されました。

  • 必要な設定だけコードとして書ける
  • 1つのジョブの中で複数のノードを扱える
  • 1つのジョブでデプロイパイプラインを表現できる
  • バージョン管理しやすい

加えて、masterが再起動してもビルドを再開できる耐久性や、ユーザーからの入力を受け付ける仕組みといった、これまでにない機能を使えるようになりました。

しかし、初期のパイプラインの構文(Scripted Pipeline)は、これまでのJenkinsの設定や他のCIツールにおけるyamlのようなわかりやすい設定に慣れたユーザーにとっては親しみにくいものでした。

withEnv(["GIT_COMMITTER_NAME = jenkins","GIT_COMMITTER_EMAIL = jenkins@jenkins.io"]) {
  node('has-docker') {
    try {
      checkout scm // checks out Dockerfile and source code
      def myImage = docker.build 'my-environment:snapshot'
      myImage.inside {
        stage('Build') {
          sh 'mvn clean install -Dmaven.test.failure.ignore=true'
        }
        stage('Archive') {
          archive "*/target/**/*"
          junit '*/target/surefire-reports/*.xml'
        }
      }
      if (currentBuild.result == null || currentBuild.result == 'SUCCESS') {
        mail to:"me@example.com", subject:"SUCCESS: ${currentBuild.fullDisplayName}", body: "Yay, we passed."
      }
    }
    catch (exc) {
      mail to:"me@example.com", subject:"FAILURE: ${currentBuild.fullDisplayName}", body: "Boo, we failed."
    }
    finally {
      deleteDir()
    }
  }
}

上の例は、Jenkins公式ブログの記事からの引用です。

内容としては、Dockerコンテナ内でmavenプロジェクトをビルドしてメール通知を飛ばすといったものですが、直観的には理解しにくいです。失敗したときにメール通知を投げるために try/catch を書かなきゃいけないというのは、プログラマなら理解はできますが、CIのジョブ設定のためにこれを書くのはなかなか辛いものがあります。

Declarative Pipeline

上記のScripted Pipelineの問題点を解決するために、ジョブの設定を記述するための新しい構文が作られました。それが、Declarative Pipelineです。

Declarative Pipelineは元々はPipeline Model Definition Pluginとして開発されていましたが、今年の2/1に1.0になりました。それと同時にPipeline Pluginのdependenciesとなり、一緒にインストールされるようになりました。

Declarative Pipelineは、よりシンプルにセクションと呼ばれるブロックベースで記述します。

pipeline {
  agent  label:'has-docker', dockerfile: true
  environment {
    GIT_COMMITTER_NAME = "jenkins"
    GIT_COMMITTER_EMAIL = "jenkins@jenkins.io"
  }
  stages {
    stage("Build") {
      steps {
        sh 'mvn clean install -Dmaven.test.failure.ignore=true'
      }
    }
    stage("Archive"){
      steps {
        archive "*/target/**/*"
        junit '*/target/surefire-reports/*.xml'
      }
    }
  }
  post {
    always {
      deleteDir()
    }
    success {
      mail to:"me@example.com", subject:"SUCCESS: ${currentBuild.fullDisplayName}", body: "Yay, we passed."
    }
    failure {
      mail to:"me@example.com", subject:"FAILURE: ${currentBuild.fullDisplayName}", body: "Boo, we failed."
    }
  }
}

こちらのDeclarative Pipelineの例も先ほどと同じ記事からの引用で、同じ内容のジョブ設定を表します。

pipeline {...} で囲われたブロック内でDeclarative Pipelineの構文は有効になり、それぞれのブロックが1つ1つのジョブの設定を表します。例えば、 agent はビルドを行うノードの設定を表しています。

Scripted Pipelineで try/catch で表現されていたビルド後の処理の設定は、Declarative Pipelineでは post というブロックでわかりやすく表現されるようになっています。

もし、Declarative Pipelineの中でScripted Pipelineのように iftry/catch のようなプログラム的な命令処理を書きたい場合は、 script というブロックを使うことによって記述できるようになっており、両方の構文のメリットを組み合わせた記述も可能になっています。なので、今後はDeclarative Pipelineをメインとして使い、Scripted Pipelineは柔軟な表現を使いたい場面だけで使うようになるのではないかと思います。

Declarative Pipelineについての詳細なドキュメントは、現時点ではJenkins公式のドキュメントPluginのGitHub Wikiを読むのがいいと思います。

まとめ

  • Jenkins PipelineにはScripted PipelineとDeclarative Pipelineの2つの構文が存在する
  • Scripted Pipelineは柔軟な表現ができる一方で複雑だった
  • Declarative Pipelineはシンプルな設定ができ、Scripted Pipelineの構文を使うこともできる

2.0がリリースされたころはDSLの複雑さが自分としても懸念でしたが、Declarative Pipelineの登場によりかなり書きやすくなったのではないかと思います。yamlほどシンプルではないかもしれませんが、そのぶん複雑なパイプラインを表現できるところが魅力だと思います。

Web上にはScripted Pipelineの構文しかない時点で書かれた情報があるので、そのあたりは注意が必要です。

次回は、少しパイプラインから離れてGitHub連携について書く予定です。