langs: en en en en ja it ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja ja en ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja en nl de ja en ja ja ja ja en en ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja es ja ja ja es ja ja ja ja ja ja ja ja ja ja ja ja fr nl en ja tr ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja de ja ja en ja ja ja ja ja ja ja en ja ja ja ja de ja ja ja ja ja ja ja hu ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja de ja ja ja fa ja ja en ja ja ja ja ja ja hbs_lat ja ja en en en ja en ja ja ja tr ja ja ja ja ja ja de ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja jv ja en ja ja ja ja en ja ja ja ja ja ja tr ja ja uz ja ja ja ja ja ja ja en ja ja tr ja ja sv ja ja ja ja ja ja ja ja de ja ja ja en ja ja ja ja en ja zh ja ja ja ja ja ja ja ja ja ja fr nl fr ja zh ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja it ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja zh ja fr nl en ja en ja ja ja ja ja ja ja ja ja en en pt ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja fi ja ja de ja ja ja en ja en ja ja ja ja en ja ja en ja ja ja en en ja ja ja en ja ja ja ja ja en ja ja ja ja ja ja ja en de ja en ja ja en ja ja ja ja en ja ceb ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja en ja en ja en ja ja ja ja ja ja ja ja ja ja cv ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja fr nl en ja it ja ja ja ja ja ja zh ja ja ja en ja en ja ja ja ja ja ja ja ja ja ja ja ja ja be ja ka ja ja en ja ja ja ja ja ja ja ja ja ja zh ja ja fa ja en en ja en en ja en en ja sv ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en en ja ja ja ja ja ja ja ja ja ja en en ja ja ja ja ja en ja ja ja ja ja ja ja fr nl en ja it ja ja ja ja ja ja ja ja ja ja ja ja en ja ja en ja ja ja en ja ja ja en ja en ja ja en ja ja ja en ja en ja de kk ja ja ja ja ja ja ja ja ja ja ja ja ja en pt zh el ja pt en ja en en en el en en en el en en ja fi ja ja ja ja ja ja zh vi de ja tt ar zh en sl en zh en ja ja ja ja ja ja ja ceb ja ja en ja ja it ja en ja en ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja fr nl ja en ja ja en ja ja ja ja ja ja ja ja ja ja ja ja it tr en ja pt is ja ja ja en it en en ja it ja it ja ja ja ja ja ja ja ja ja ja ja

text:
例4)Xcode12 x XcodeGenで起きたCycle Inside App問題
そして、ワークフロー1にある「Bitrise Wait for Buildステップ」で他のワークフローが終わるのを待機します。 また、ここで他のワークフローの成果物をまとめてテストケース数などを計算するようにしています。
これはgit cloneステップの「Checkout options」にある「Limit fetching to the specified number of commits」で指定できます。 これにより2〜3分かかっていたのが20秒前後にまで削減されました。
結果、メンションをしすぎてしまい逆に放置されてしまう原因になってしまいます。
そこで、ワークフローが失敗してはいけないような「デフォルトブランチ」や「リリースブランチ」においてだけ、失敗した時点で開発メンバーがメインで利用しているチャンネルにSlack通知をするようにしました。
これで、問題に気づきやすくなりました。
最終的には問題が起こらないようにする必要があります。 通知をおこなうのではなく、そもそも「失敗したものがマージされないようにする」のが望ましいです。 しかし、この段階でその設定までおこなうのはまだ時期尚早なため、まずは知ってもらうというのを優先しました。
行いたかった「Step1」と「Step2」は次のとおりです。
Step1:PR時点で「ビルド」「テスト」のワークフローを動かす
Step2:「ビルド」「テスト」のワークフローが失敗したものはマージできないようにする
これらがどうなったかについて次に説明していきます。
Step1に対する結果
実行時間の短縮により「課題1」は解決しました。 そこで、PRに対してもワークフローを実行するようにしました。
ただし、一度に「ビルド」と「テスト」を設定するのではなく、まずは「ビルド」を動くようにしました。 PR時に「ビルド」を動かすようになってある程度たってから、追加で「テスト」についても動くように設定しました。
Step2の対応と結果
「Step1」が達成した段階時点では、まだ「Step2」はそこまで必要と認識されていない状況でした。 ある程度、今の状態が慣れるのを待ちつつタイミングを待つこととしました。
ブランチに失敗したままのコードがマージされるケースは、この時点でもたまに起きていました。 それでも、まだStep2についてはおこなえていない状態でした。
しかし、実際に検証前にビルドができないといったような「誰もがこの状態は良くない」と思える事件が発生した時点で、今後の予防として「Branch Protection Rule」の導入を提案しました*2。
この時点ではPR時に「ビルド」「テスト」が動くようになってからある程度たっている状態でした。 これらのワークフローの実行時間があまりかかってないことを共有し、導入することになりました。
Branch Protection Ruleの導入
一度に導入するとなにかあったときの対応コストは多くなります。 その結果として導入をやめようという流れにもなりかねないため、少しずつ設定を追加するようにしました。
まずは次の設定を入れました。
PRの向き先のブランチ:「リリースブランチ」
「Require status checks to pass before merging」をオン
GitHub Commit Statusをそれぞれ独自におこなっているので設定はかんたん
設定を入れた状態にし、なにかしら問題が起きたら即時対応をするようにしました。 例えば、Bitrise起因でビルドが出来ないなどの対応があります。
上記の設定を導入し、ある程度たったのちに次のように拡大をしていきました。
PRの向き先のブランチを拡大:「リリースブランチ」「デフォルトブランチ」
「Require status checks to pass before merging」
この設定を入れて、慣れてくるようになるとエンジニアメンバーからも提案が出てくるようになりました。 そこで、次の設定を入れることにしました。
Require pull request reviews before merging
レビュー必須(approve 1件以上)
これにより「ビルド」「テスト」ができないもの、コードレビューがされていないものは基本マージができなくなり、事故が起こることは減りました。
これで当初予定していた「Step1」と「Step2」のそれぞれの対応が終わりました。
求める姿を想定し、そこに進めていくのは必ずしも一直線とは限りません。
その状態自体が本当に自分たちにとって良いのかも分からないこともしばしばありますし、その状態にすることによる大変さがあるかもしれないと思うかもしれません。
今回は、上述したように少しずつ対応を進めていきました。 そして、少しずつメンバーに慣れていってもらうようにしました。
その結果として、当初予定していた「求める姿」になりました。
この状態がベストではなくまだ先の「求める姿」があると思っています。 また、プロジェクトの状態が変わっていけばまた変えるべき箇所も出てくるとは思います。
今後も現在の状況を見ながら、「求める姿」を考えて対応を進めていければと思います。
最後に、上記のような活動に興味を持ってくれた方、一緒に働いてみたいなと思ってくれた方。 現在、SWETはメンバーを募集中ですのでぜひ応募ください。
*1:Bitriseではリポジトリにあるbitrise.ymlを正として利用する設定がありますが、弊社の場合はGitHub:eを利用しており本機能をそのまま利用できません。
*2:Branch Protection Ruleを用いることで、PRのコードがCI/CDサービスで成功していない場合は特定ブランチにマージさせないといった設定できます。
swet-blog 2021-11-05 16:32
8年ぶりの新卒としてSWETにjoinした話
Special Article
はじめまして、2021年に新卒としてSWETに加わったIKです。 今回はSWETに入って感じたこと、思ったことなどを新卒目線で書いていきたいと思います。
学生時代にVim scriptばかり書いていた新卒です。 主に、ソフトウェアのメンテナンスに興味をもち、OSSにパッチを送る活動をしていました。
パッチを送った主なリポジトリ
vim-airline
vim-devicons
また、VimConf2019に登壇し、以下の発表をしました。
Grown up from Vim User to Vim plugin developer side
しかし、一般的なWebシステムやアプリ開発には疎い、そんな状態で入社しました。
どうしてSWETへの配属を志望したのか
私は学生時代、1からソフトウェアを作ることよりも、ある程度形になったソフトウェアのメンテナンスに参加することの方が多かった傾向にあります。 そこで行ったメンテナンスの内容を以下に示します。
具体的なメンテナンスの内容
feature requestに応える
Pull Requestのレビュー
既存のドキュメントの改善
ソフトウェアのメンテナンスに参加することで、メンテナンスのフェーズは1からソフトウェアを作成するフェーズよりも遥かに長いこと、メンテナンスを継続することはとても大変であることを思い知りました。 その経験から、ソフトウェアのメンテナンスの負担を減らすこと、開発時に発生する手戻りを減らすことはソフトウェア開発が行われる限り常に要求されることだと考え、それらに興味を持つようになります。 その私の考えがSWETのミッションとマッチしていると考えたため、私はSWETへの参加を志望しました。
ソフトウェアテストを起点とし、DeNAのサービス全般の品質向上とエンジニアの開発生産性向上に対して貢献していくことをミッションとする
SWETに配属されてどんな仕事をしているのか
Unityプロジェクトに導入するための静的解析器の開発をしています。 C#向けの既存の静的解析器はいくつかOSSとして公開されていますが、以下の理由により自作する必要があります。
社内独自フレームワークに起因する問題を検査できない
既存の静的解析器のルールでは、検出したい問題を見逃してしまうことがある
では、自作の静的解析器を作成するために行った/行っているフローを以下に示します。
ECMA334やMicrosoft社のドキュメントを参考に、C#の構文を調べながら、診断の対象になるものを決めて、洗い出す(ここが一番大変)
仕様をデシジョンテーブルにまとめ、テストケースを洗い出す
作成したデシジョンテーブルをもとにテストコードを1件ずつ追加
静的解析器を実装し、テストを通す
静的解析器を開発プロジェクトで使ってもらうためには、適用する開発プロジェクトの人の信頼、すなわち静的解析器に対して好印象を持って貰う必要があります。 そのため、正確に静的解析ができているかを検証し、証明するためにテストを書くことは重要です。
要約すると、テストのノウハウを学びつつ、静的解析器を実装する仕事をしています。
SWETの人たちが、新卒目線でどう見えるか
ミスを未然に防ぐことや手戻りを防ぐことに意識をおき、そういった可能性がある箇所に対しての改善を妥協しない人が多い印象です。 特にメンターから教わったことの中から印象に残ったものを以下に示します。
テストメソッドの命名をしっかり意識する
SWETにjoinする前の自分は、テストメソッドの命名が雑でした。極端な例を出すと、goodcaseとかbadcaseなどと言った命名をしていました。
FizzBuzzを使った例
[TestMethod] public void GoodCase() { var actual = FizzBuzz(3); Assert.Equal("Fizz", actual); }
このように、テストメソッドの命名が雑だと以下のようなデメリットがあります。
テストが失敗したとき、失敗したメソッド名やメッセージを見ただけでは失敗箇所の特定が行えず、深くコードを追う必要がある
そこで、テスト対象メソッド名_テスト対象に与える条件_期待する結果のような命名を意識しました。
[TestMethod] public void FizzBuzz_入力値が3_Fizzを返す() { var actual = FizzBuzz(3); Assert.Equal("Fizz", actual); }
これにより、上記のデメリットを解消できます。
テストの無いリファクタリングはリファクタリングとはいわない
この言葉は自分にとても刺さりました。 リファクタリングとは、プログラムの外部から見た振る舞いを変えずに、内部構造を変えることだとした上で話しを進めます。
テストを書き、実行することで、リファクタリングを試みた際に発生したリグレッションを検知できます。 しかし、当時の自分は自動テストを実施せずに目視のみの確認でリファクタリングを完了としていました。 そのため、抜け漏れが発生し、リグレッションを起こしてしまったことが多々ありました。
リグレッションを放置していると、ボディーブローのように後々効いて、気づいたときには巨大なバグになっていることも少なくありません。 こういった状態を作らない、妥協しない姿勢をSWETの方から見習い、吸収していきたいと思いました。
新卒でSWETに入って良かったと感じたこと
テストケースの考え方や、テスト自動化の導入方法は、どんなソフトウェア開発にも必要です。 また、テストを導入するためには、テストを導入しやすい環境を作らなければなりません。
これらのテクニックは、これからも陳腐化することはないスキルだと自分は確信しています。 そういった潰しの効くスキルを基盤として身につけながら業務に携わることができてとても良かったと感じています。
また、こういったテスト技術を基盤として様々なソフトウェアに関わることで、様々なソフトウェアの知識を吸収ができるのも良さだと考えています。
新卒でSWETに入って、うまく仕事を遂行できるか不安ではありましたが、毎日新しい発見がありとても学びになっています。 また自分の施策が、ソフトウェア開発を促進するための施策に関われていることを実感でき、今の仕事にとてもやりがいを感じています。 新卒でSWETに入ったことを自分は正解だと感じており、今後もSWETで学んだことを様々なソフトウェア開発の促進のために生かしていきたいと考えています。
SWETは中途しか採用していないイメージを持たれている方が多いのかも知れませんが、新卒で入ることも可能です。SWETは学びが多い部署だと思っているので、新卒でSWETに入る選択もありだと思います。 もしSWETに興味を持った方がいれば、是非お話しましょう!
swet-blog 2021-09-15 17:10
大きなGitリポジトリをクローンするときの工夫を図解します
こんにちは、SWETでCI/CDチームの前田( @mad_p )です。 SWETではCI/CDチームの一員として、Jenkins運用のサポートや、CI/CD回りのノウハウ蓄積・研究をしています。
Gitリポジトリをクローンすると、ローカルフォルダにはそのリポジトリの全体がダウンロードされ .git というフォルダに格納されます。ブランチをチェックアウトすると、ブランチ内のファイルがワーキングツリーとして展開されます。この様子を図にするとこのようになります。
この .git とワーキングツリーの使うディスク容量を節約しようというのが今回のお話です。特にJenkinsにおいて、大きめのGitリポジトリをクローンしてくる場合に課題があり、いろいろ工夫してみたので、その結果を紹介します。同じCI/CDチームの加瀬による記事「大規模リポジトリで高速にgit cloneするテクニック」と内容的に重なる部分もあるので、そちらの記事も参考にしてください。
今回、Jenkinsでの設定方法を多めに解説していますが、紹介する工夫はJenkins以外でも使えるものです。図を見て仕組みを理解しておくだけでも、後で役に立つと思います。
Gitリポジトリは大きくなっていく
アプリコードやアセットのリポジトリは、開発が進むにしたがって大きくなっていきます。これはゲーム開発では特に顕著です。とあるタイトルのアセットリポジトリは、そのままクローンすると .git が17GiB、チェックアウトした部分も含めると45GiB程度になっています。これは日々大きくなっていきます。
リポジトリ内のオブジェクトを簡略化して図にすると以下のようになります。丸がコミット、四角がファイルに対応します。
リポジトリが時間とともに大きくなっていくのは、以下のような要因によるものです。それぞれに対して、何か対策があるか見てみましょう。
コミットされるファイル数が多くなる
これは必要だからコミットされているので、仕方がありません
コミットされるファイルが大きい
これの対策としては、大きいファイル(画像、音声データなど)をGit LFSに置くことで、リポジトリ内のオブジェクトとしては保存しない、という方法があります。LFSについては後でもう少し詳しく見てみます
これも長く開発が続いていくと増えるものなので、仕方がありません
コミットされるファイル数が多くなる方向、歴史が長くなる方向の2つの軸を意識しておくと、この後紹介する工夫の理解がしやすくなると思います。
Gitリポジトリが大きくなると様々な問題が発生します。
クローンに時間がかかり、待ち時間やJenkinsジョブ実行時間が長くなる
クローンしたフォルダのディスク使用量が多くなり、ディスク枯渇が発生しやすくなる
これに加え、Jenkinsエージェント特有の事情として以下があり、巨大なリポジトリのクローンによる困り事は増幅されることになります。
Jenkinsではジョブごとにワークスペースが分離される。同じリポジトリを使うジョブが複数個あると、ひとつのリポジトリでも複数回別のフォルダにクローンされる。その回数分ディスク容量を必要とする
ワークスペースは、特に明示しない限り、次回の実行に備えて保存される。次回実行時にリポジトリのフェッチの負荷が低くなるというメリットとなる(クローン済の .git を使って差分だけを取ってくればよいため)。一方で、取っておく分のディスクを消費する
Gitリポジトリからローカルフォルダへのダウンロード量や通信量を減らすことで、これらの困り事を軽減できます。クローン時、チェックアウト時、LFSの順で、どのような工夫ができるかの方法を紹介します。また、その結果どのように節約できるのかを図にして見ていきましょう。
GitHub公式ブログ
ここで取り挙げる節約方法はGitHubブログの以下の記事でも解説されています。図が多くわかりやすいので、参照してみてください。本記事中の図も、この公式ブログの図を真似しています(わかりやすさのため、本記事ではツリーオブジェクトなど一部省略しています)。
コミットはスナップショットであり差分ではない
パーシャルクローンとシャロークローンを活用しよう
Git clone: データで見るクローンの仕組み
クローン時のディスク・ダウンロード節約方法
大きめのリポジトリをクローンする場合のコツについて説明します。
git clone では通常、前述したオブジェクトの全体をダウンロードしてきて、 .git/objects 配下に置きます。以下では、この全部を取得するのではなく、一部を取得するためのテクニックを紹介します。これによって、クローン後のディスク使用量だけでなく、クローン時のネットワーク転送量(すなわち時間)も節約できます。
シャロークローン(shallow clone)
最新版だけを取ってきて、過去の歴史を取ってこない方法です。取得したい歴史の長さ(深さ)をdepthというパラメータで指定できます。通常は1でよいでしょう。シャロークローンの動作を図にすると以下のようになります。
歴史の一番新しいほうからdepth分だけをサーバーから取得し、 .git 内に置きます。取得されない部分を図中では点線で表現しています。最新情報だけ取ってくるので、取得するオブジェクト数を節約できます。これは大きくなるリポジトリを歴史の長さ方向に限定して取得することに相当します。
シャロークローンのやりかた
git clone 時に --depth オプションで取得したい歴史の深さを指定する
git clone --depth=1 git@github.com:org/repo.git .
Jenkinsfileによる方法
checkout ステップに CloneOption を渡して shallow に true 、 depth を指定
checkout([$class: 'GitSCM', extensions: [[$class: 'CloneOption', shallow: true, // ← shallow cloneを指定 depth: 1, // ← depthを指定 timeout: 60]], branches: [[name: "feature/mybranch"]], gitTool: 'Default', userRemoteConfigs: [[url: 'git@github.com:org/repo.git']]])
JenkinsのFreestyleジョブでの設定方法
「ソースコード管理」の「追加処理」で「Advanced clone behaviors」を追加し、shallow cloneをチェック、shallow clone depthを入力
シャロークローンの欠点
シャロークローンには以下のような欠点があります。歴史に対する操作を使うことがわかっている場合は利用しないほうがいいかもしれません。
シャロークローンで取ってきていると、歴史をあやつる作業はできなくなる
git log で過去のコミットを見る
過去のコミットをチェックアウトする
git diff で差分を見る
公式ブログの説明によると、シャロークローン後のフェッチで、場合によっては結局過去の歴史を全部取ってしまうような場合もあるとのこと
一度クローンした場所で、追加でフェッチをするような用途にはシャロークローンは向かない。つまりJenkins向きではない。一方でCircleCIなどクローンした結果を一度しか使わない環境では有効
なお、いったんはシャロークローンしたものの明示的に全部を取り直したいという場合は、 git fetch に --unshallow オプションをつけます。
git fetch --unshallow
パーシャルクローン(partial clone)
コミット内の各ファイルは、そのメタ情報(ツリーオブジェクト)とファイル実体(ブロブオブジェクト)から成っています(上述のGitHubブログを参照)。パーシャルクローンは、ブロブやツリーオブジェクトを必要な部分のみ取得します。ローカルには、必要に応じてオブジェクトを取得するという情報(下の図では中空の四角で表現)が記録され、checkoutやdiffなど、実際に必要になった時点でオンデマンドにブロブオブジェクトがダウンロードされます。
パーシャルクローンは操作に必要な要素のみを取得するので、歴史の長さ×ファイル数を2次元的な広がりと見て、必要な部分だけを取得することに相当します。この図はクローン後、いくつか操作して必要なものが取得された状況を表現しています。
パーシャルクローンのやりかた
ここではブロブを必要な部分のみ取得し、ツリーはすべて取る方法(ブロブレスクローン)を紹介します。
git clone 時に、 --filter オプションでブロブを取得しないことを指定する
git clone --filter=blob:none git@github.com:org/repo .
Jenkinsfileによる方法
checkout ステップには機能がない。 sh ステップでCLIのgitを利用する
Freestyleジョブでの設定方法
同様にシェルスクリプトからgitを利用
パーシャルクローンの欠点
パーシャルクローンには以下のような欠点があります。
git diff など、ファイルの内容を必要とする操作をすると、オンデマンドでダウンロードされる
その時点でネットワーク接続が必要であることと、ダウンロード時間がかかることに注意
リファレンスリポジトリ(reference repository)の活用
Jenkinsでは複数のジョブから同一のリポジトリ(アプリソースやアセットなど)を参照することが多くあります。その場合、ジョブごとに別のフォルダに同じリポジトリをクローンしてくることになります。2回クローンした状態を図にするとこうなります。
このとき、 .git はフォルダAとBの下にそれぞれ作成され、歴史を記述したオブジェクトはそれぞれの下に保存されます。ブランチを切ったりコミットしたり、特定のブランチだけフェッチすると、内容は完全に同一とはならないのですが、サーバーからコピーした分は同一のものがディスク上で別の場所に複数回保管されていることになります。特定のリポジトリに関するジョブが十数個にもなると、その数だけ複製を持つことになります。
Gitにはすでにクローン済の .git/objects を参照する機能があります。これを使うと、次の図のようになります。
B/.git/objects の下に、「 A/.git/objects を見ろ」というファイルが記録され、そちらも参照してくれるようになります。AになくてBに必要なオブジェクトはサーバーからダウンロードされ、Bの .git/objects の下に保存されます(このため、Aの情報が多少古くても問題ありません)。これによって、Bの初回クローンは圧倒的に速くなり、ディスクも大きく節約できます。このとき、フォルダBから見てフォルダAを「リファレンス」と呼びます。リファレンスを利用したクローンを、この記事では「リファレンスクローン」と呼ぶことにしましょう。
リファレンスクローンのやりかた
フォルダBにクローンする時点で、すでにフォルダAにクローン済である場合、Aのパスをリファレンスとして指定できます。
git clone 時に、 --reference または --reference-if-able オプションでリファレンスとして使うクローン済みのフォルダを指定する
git clone --reference-if-able=/path/to/A git@github.com:org/repo ./B
--reference で指定するとリファレンスが存在しなければエラーとなる
--reference-if-able では存在しなければ無視される(警告のみ)
サブモジュールの取得にリファレンスを使いたい場合は、サブモジュールごとに指定する
git submodule update --init --reference=/path/to/A -- submodule/path
Jenkinsfileによる方法
CloneOption に reference を指定できる。リファレンスが存在しないと無視される(エラーメッセージが出るが処理は続行される)
checkout([$class: 'GitSCM', extensions: [[$class: 'CloneOption', reference: "/path/to/A", // ← リファレンスのパスを指定 timeout: 60]], branches: [[name: "feature/mybranch"]], gitTool: 'Default', userRemoteConfigs: [[url: 'git@github.com:org/repo.git']]])
Freestyleジョブでの設定方法
「Advanced clone behaviors」→「リファレンスリポジトリのパス」に設定する。存在しないと無視される
通常どおりにクローンしたフォルダに対して、後からリファレンスを加えることもできます。
.git/objects/info/alternates というファイルを作成し、リファレンスの .git/objects のフルパスを書き込む
サブモジュールの場合は .git/modules/ /objects/info/alternates
alternates 作成後、以下のコマンドを実行すると、リファレンスに存在するオブジェクト(重複分)を削除できる
git repack -a -d -l
リファレンスクローンの注意点
AをリファレンスとしてBをクローンした後に、A側でオブジェクトが削除されると、Bから参照しようとしたときにオブジェクトが見つからずにエラーとなります。 git pull などの操作をきっかけとして、A側で git gc が自動実行されることがあり、これが発生します。フォルダAをまるごと削除した場合も同様です。 その場合、以下のようなエラーにぶつかるでしょう。
fatal: bad object HEAD
fatal: bad object
fatal: unable to read
一度こうなってしまうと修復はなかなか大変なため、リファレンスクローンは事情をよく知って使う必要があります。 git help clone で --reference の項目を見ると、 --shared と同様の注意が必要と書いてあり、その注意書きはこうです。要約すると「A側でブランチが削除されるとB側で必要なものが消され、Bが壊れる場合がある。何をやっているかよく理解して使いなさい」ということです。
NOTE: this is a possibly dangerous operation; do not use it unless you understand what it does. If you clone your repository using this option and then delete branches (or use any other Git command that makes any existing commit unreferenced) in the source repository, some objects may become unreferenced (or dangling). These objects may be removed by normal Git operations (such as git commit) which automatically call git maintenance run --auto. (See git-maintenance(1).) If these objects are removed and were referenced by the cloned repository, then the cloned repository will become corrupt.
これらの問題を回避するために、リファレンスで指す先はリファレンスで使われることだけに特化したミラーフォルダとして用意するのがよいと思います。組織的に運用しないと安定した稼動は難しいかもしれません。以下に、自分達のプラクティスを紹介しますので、参考にしてみてください。
ローカルマシン(Jenkinsエージェントなどごと)にリファレンス用途のクローン(以下ミラー)を作成する
export MIRROR_DIR=/path/to/mirror/dir mkdir -p $MIRROR_DIR git clone --no-checkout git@github.com:org/repo $MIRROR_DIR/repo (cd $MIRROR_DIR/repo; git config gc.pruneExpire never)
ミラーでは git config gc.pruneExpire never しておくことで、自動gcによるオブジェクトの削除が起こらないように設定する
環境変数 MIRROR_DIR でミラー群の位置を指すよう、 .bash_profile や、Jenkinsのノード管理で定義しておく
ミラーはベア(bare)リポジトリとしてクローンするか、 --no-checkout で作るとワーキングツリー分のディスクを節約できる
定期的にフェッチするジョブをしかけておく
.git/gc.log に「too many unreachable loose objects」が出力されるが、これは無視してよい。リファレンスする側のフォルダで使っているオブジェクトがloose objectsとして検出されることが多い
ミラーにtmpオブジェクトが残ってしまったような場合など、どうしてもgcやpruneコマンドを使いたい場合は、以下のオプションをつける
git prune --expire=never; git gc --no-prune
クローン時は --reference-if-able=$MIRROR_DIR/repo オプションをつける
リファレンスクローンを利用する場合、サーバーから取得する歴史はリファレンスにないオブジェクトのみになります。このため、シャロークローンやパーシャルクローンを利用しなくても十分な節約になります。プロジェクトで利用する全部のリポジトリについてミラーを作る必要はなく、複数の場所に何度もクローンされやすいリポジトリを選んで設定するだけで、大きな効果を得ることができるでしょう。
チェックアウト時のディスク節約方法
ここまで、クローンのときの工夫を見てきました。続いてチェックアウトのときの工夫を紹介します。
ファイル数が多くなったリポジトリは、チェックアウトした結果のワーキングツリーもかなりディスクを使うことがあります。通常のチェックアウトの様子を図に示します。チェックアウト時にはHEADから参照されるすべてのファイルのコピーがワーキングツリーとしてコピーされます。
スパースチェックアウト(sparse checkout)
必要とするファイルがリポジトリ内の一部だけである場合、例えば特定のシェルスクリプト数個だけの場合、リポジトリ全体ではなく、一部のフォルダやファイルだけをチェックアウトすることで、ワーキングツリーを小さくできます。これをスパースチェックアウトと呼びます。
スパースチェックアウトはファイル数方向を限定してワーキングツリーを小さくすることに相当します。歴史オブジェクトは全部ローカルに持ってきているので、歴史をあやつる作業もできます。逆に言えば、歴史の取得や .git の容量は節約していません。
スパースチェックアウトについては、こちらのブログ記事が参考になります。
モノリポ時代に知っておくと便利な「git sparse-checkout」
スパースチェックアウトのやりかた
CLIによる方法 (上記ブログ記事も参照してください)
git clone 時に --sparse オプションでスパースチェックアウトであることを、 --no-checkout オプションで初回のチェックアウトしないことを指定する
クローン後 git sparse-checkout add でチェックアウトしたいフォルダを指定する
必要なフォルダやファイルを指定するにはパターンも使える。パターンの書き方の詳細は git help sparse-checkout を参照
git clone --sparse --no-checkout git@github.com:org/repo . cd repo git sparse-checkout add want_folder1 want_folder2 git checkout
Jenkinsfileによる方法
checkout([$class: 'GitSCM', extensions: [[$class: 'SparseCheckoutPaths', sparseCheckoutPaths: // checkoutしたいフォルダ [[path: "want_folder1"], [path: "want_folder2"]] ]], branches: [[name: "feature/mybranch"]], gitTool: 'Default', userRemoteConfigs: [[url: 'git@github.com:org/repo.git']]])
Freestyleジョブでの設定方法
ソースコード管理の「追加の処理」から「Sparse checkout paths」に設定
前述のパーシャルクローンと組み合わせて使うと、さらにディスクと通信時間を節約できます。
このように組合せて使うと、ファイル数方向、歴史の長さ方向の両軸で限定したことに相当します。
LFS (Large File Storage) の活用
Gitリポジトリに大きいバイナリファイルをコミットしていくと、その歴史で .git は大きくなってしまいます。また、GitHubにはひとつのファイルの大きさは100MiBまでという制限があるため、これを超える大きさのファイルはそもそもコミットできません。ゲームのアセットなど、画像や音声ファイルを多く扱うリポジトリでは、ここで説明するLFSを利用するのがよいでしょう。
LFSはファイルの実体を別のストレージに置き、その参照(SHA256ハッシュ)のみをコミットします。図にすると以下のようになります。 .git 内には参照しか入らないため、 git clone の段階ではファイルの実体はダウンロードされません。 git lfs pull により、必要なオブジェクトの実体がダウンロードされる仕組みです(通常はクローン直後のチェックアウトで自動的に行われます)。
Git LFSの設定は、リポジトリ作成直後から行うのがおすすめです。先に大きなオブジェクトをコミットして、後からLFS化しても、コミットしてしまった分は大きなオブジェクトが歴史に残ってしまうからです。以下の手順でLFSの設定ができます。
マシンにgit-lfsをインストールする
brew install git-lfs , apt install git-lfs など
アカウントごとにLFSを使うことを設定する
git lfs install
チェックアウト時に自動的にLFSからダウンロードされるようになります
LFSを導入したいリポジトリで、特定の拡張子のファイルをLFSに格納するよう設定する
git lfs track "*.psd" "*.ogg"
通常どおりコミットしてプッシュすると、設定した拡張子のファイルは、実体がLFSに送られる
クローンした側ではチェックアウト時に自動的にLFSファイルが取得される。明示的に取得する場合は以下コマンドを実行する
git lfs pull (現在チェックアウトしているブランチのオブジェクトを取得し、ワーキングツリーに置く)
LFSの設定・動作ついては、以下の公式ドキュメントや記事が参考になります。
Git Large File Storage を設定する
Git LFS をちょっと詳しく
すでに運用中のリポジトリを後からLFS化したい場合、 git lfs migrate というコマンドが使えます。LFSのマイグレーションは手順が多いため、ここでは説明しません。以下のチュートリアルが参考になるでしょう。
Migrating existing repository data to LFS
LFSのディスク節約方法
git lfs pull の動作は以下の2つに分けられます。
git lfs fetch
現在のブランチで利用されているLFSオブジェクトをダウンロードし、ローカルストレージに置く
ローカルストレージは通常、クローンしたフォルダの .git/lfs (サブモジュールでは .git/modules/ /lfs)
git lfs checkout
ローカルストレージからワーキングツリーにオブジェクトをコピーする
git lfs pull は git lfs fetch の後 git lfs checkout を行うことに相当します。
これを図にするとこうなります。 git lfs checkout でファイルがコピーされるため、LFSで管理されているファイルは、ローカルストレージ内とワーキングツリー内、通常2個ずつディスクに置かれることになります。
ワーキングツリーのディスクを節約する
ワーキングツリー内のファイルは書き換えてコミットする使い方もあるので、 git lfs checkout では(ハードリンクなどではなく)ファイルをコピーすることが必要です。CIなど、read-onlyの作業しかしないことがわかっている場合には、コピー分のディスクを節約したくなります。
macOSではAPFSというファイルシステムの機能で、ファイルを複製するときに同じディスク領域を使い、ディスクを節約できます。書き込みが発生した時点でcopy-on-writeする動作になります(cpコマンドの -c オプション)。 git lfs dedup というコマンドを実行すると、LFSのローカルストレージとワーキングツリーを比較し、ファイル内容が同一のとき cp -c を使ってひとつにまとめるということをしてくれます。
ローカルストレージを節約する
ローカルストレージには過去のチェックアウトで使ったデータがたまっていくので、時々プルーン(おそうじ)したほうがよいかもしれません。とはいえ、よほど大きくなっていない限り、明示的にプルーンする必要はありません。
git lfs prune
ローカルストレージを共有する
同一のリポジトリをJenkinsの複数ジョブなどで複数回クローンすると、ローカルストレージはそれぞれの .git 下に作られます。つまり、クローンした回数分のオブジェクトがディスク上に保存されます。
ローカルストレージを変更し、フォルダAとBの両方で共有するよう設定すると、この重複分を減らすことができます。
git config --local lfs.storage /path/to/shared/storage
LFSのローカルストレージを特定のパスに設定します
同じリポジトリに対するクローンの各フォルダで、同一のパスを指定してこのコマンドを実行するとよいでしょう。
この設定を使うと、大きくなりがちなLFSのローカルストレージを共有して、全体の容量を減らすことができます。なお、ストレージの共有を使っている場合は git lfs prune しないほうがよいです。図中のフォルダAで git lfs prune した場合、フォルダBからしか参照されていないオブジェクトは消されてしまいます(次回チェックアウト時にダウンロードし直されるので、エラーにはなりません)。
この設定をグローバルに行うこともできます。
git config --global lfs.storage /path/to/shared/storage
--global に設定すると $HOME/.gitconfig に設定が書き込まれ、そのアカウントでクローンするリポジトリすべてのLFSローカルストレージが同一の場所になります。これには以下のように利点・欠点がありますので、それに注意して利用するとよいでしょう。
クローンするたびにいちいちローカルストレージの設定をしなくてよい
サブモジュールでもLFSを使っている場合、すべてのサブモジュールで設定する必要がない
異なる元リポジトリに由来するオブジェクトが、ローカルストレージ内で混在する
機密レベルの異なるリポジトリを扱う場合には、混在するとよくないかもしれない
なお、SHA256ハッシュを使っているので、別のオブジェクトが同名になってしまう(ハッシュの衝突)心配はない
.git は気がつくと大きくなってしまうものです。うまく工夫することで、ディスク容量や転送時間を減らすことができます。歴史の長さとファイル数の増加どちらがより大きな要因であるか、また、クローンしたフォルダをどう使うかなどを考慮して、シャロークローンやパーシャルクローンなど適切な方法を選択しましょう。
Jenkinsエージェントのような環境では、同一のリポジトリを複数のフォルダにクローンすることが多く、 .git はその数ぶんだけ作られてしまいます。リファレンスクローンで共有したり、LFSのローカルストレージを共有すると、この「複数回」が原因となる重複を減らすことができるでしょう。
これらの工夫を使って、冒頭で例に出したタイトルでは、Jenkinsエージェントあたり300GiB程度のディスク容量を節約できました。いくつかのリポジトリが何度も何度もクローンされていたので、リファレンスクローンの効果が大きく250GiB程度の節約、LFSローカルストレージの共有が50GiB程度の節約になりました。スパースチェックアウトはまだ十分活用できていませんが、改善の余地があるのだと前向きに考えています。
swet-blog 2021-07-12 12:00
タクシーアプリ「GO」Android版へ自動テストを導入するまでの道のり
こんにちは、Androidチームの田熊(fgfgtkm)と外山(sumio)です。SWETのAndroidチームでは、Androidのプロダクトに対して自動テストのサポートをしています。
この度株式会社Mobility Technologiesが提供するタクシーアプリ「GO」のAndroid版に対する、おおよそ2年間に渡る取り組みが終了しました。
本記事では、この取り組みで行ってきた次の2点を紹介したいと思います。
「GO」のAndorid版に対してどのような自動テストを導入したか
開発チームに自動テストを定着させるまでにやったこと
タクシーアプリ「GO」に対しての自動テスト導入
SWETのAndroidチームは「社内のAndroidエンジニアが自動テストを書くことを習慣化している」ことをゴールの1つとして、自動テストを普及させるための取り組みをしています。 その中でAndroidのテストナレッジの社内展開を目的としたハンズオンを実施してきました。
テストの社内普及のための取り組みとして、Androidテストハンズオンを実施しました
テスト社内普及プロジェクト第2弾! Android UIテストハンズオンを実施しました
しかし、社内の多くのチームはテストが書きにくい設計になっているという課題を持っており、既存機能にテストを書くためには設計を改善する必要がありました。(参考:テストの社内普及のための取り組みとして、Androidテストハンズオンを実施しました)
今回紹介する「GO」(旧サービス名:MOV)も例外ではありません。 ハンズオンではカバーしきれない現場の課題を解決して自動テスト導入を促進したい、そして「GO」をそのモデルケースにしたい、と考えたのが本取り組みのきっかけです。
それから約2年間、開発チームと協力しながら次の施策を実施しました。
コード改善 + ユニットテストの導入
スクリーンショットテスト
それぞれどのようなことをやってきたか、具体的にお話したいと思います。
コード改善 + ユニットテストの導入
当時の「GO」は歴史的経緯から一貫したアーキテクチャが定められておらず、多くの処理がFragmentやActivityに記述されていました。メイン機能となる地図を表示している画面(以降、地図Fragmentと呼びます)も同様で、配車に関連する複雑なロジックが地図Fragmentに実装されていました。
最初に行ったことは、この地図Fragmentに実装されたロジックを別クラスに切り出した上でユニットテストを書けるようにし、MVVMの構成にすることでした。以降、地図Fragmentや関連する画面クラスの改善を地道に進めていきました。
画面から切り出したほうが良い処理をピックアップし、画面からの分離⇔ユニットテストを実装する。そのサイクルを繰り返すことで、少しずつユニットテストが書けるコードを増やしていきました。
こうしてテストコードが追加された結果、そのテストコードを参考に開発メンバー自身が自発的にユニットテストを実装してくれるようになりました。(具体的な数値については「自動テストを書くことを習慣化できたか?」のセクションで紹介します)
また、もともと開発チームでもMVVMアーキテクチャにしていきたいという思いがあったため、開発チーム主導でMVVM化がどんどん進んでいき、今ではほとんど全ての機能がMVVMアーキテクチャで実装されています。
改善した内容の一部はMOV Android版に対する「コード改善+テスト導入」の取り組みの紹介でもまとめていますので、是非ご参照ください。
スクリーンショットテスト
ユニットテスト導入をある程度進めたタイミングで、一度リリース前検証で出た不具合チケットを整理しました。その結果、画面まで結合させないと発見が難しい不具合、ViewModel以下のレイヤのユニットテストでは発見の難しい不具合が高い割合で起票されていることがわかりました。
Androidアプリ開発においてこのような不具合はつきもので、問題を早期発見するためにUIテストを導入できないか考えました。そしてUIテスト実装のアイディアについて開発チームと議論を重ねた結果、スクリーンショットを活用したテスト(以降、スクリーンショットテストと呼びます)を導入することで合意しました。
SWETではスクリーンショットテストを導入するために、主に次の2つのことを実施しました。
スクリーンショットテストを書きやすくするための基盤を整備する
スクリーンショット一覧レポートの確認や画像差分の比較ができるようにCI/CDを整備
スクリーンショットテストを書きやすくするための基盤
スクリーンショットテストは画面が任意の状態になるようにデータをセットし、さらに非同期処理やレンダリング完了まで待った上でスクリーンショットを撮る必要があります。ユニットテストと比較するとテストコード側でセットアップする必要のあることが多くなり、ボイラープレートが増えてしまいます。
我々はできるだけスクリーンショットテストを書く大変さを軽減したいと考え、書きやすくするための基盤の実装に着手しました。そして開発チームと一緒に様々なパターンのスクリーンショットテストを実装しながら、機能をブラッシュアップしていきました。
この基盤は次のことを実現しています。
Junit5のExtensionとして提供され、テストに適用するだけで「GO」で安定してスクリーンショットテストを取得するために必要なセットアップが完了する(非同期処理の待ち合わせ設定など)
Activity・Fragment・DialogFragmentそれぞれの起動を簡潔に書けるようにする手段の提供
画面単位・View単位など様々なスクリーンショット取得方法の提供
テストでよく使用されるEspressoのコードを汎用化
基盤の実装の一部はGOGO Screenshot Test for AndroidとしてOSS化しています。是非ご参照ください。
取得したスクリーンショットは一覧化したレポートを作成することで、エンジニア以外のメンバーでもデザインのレビューがしやすくなります。 また、reg-suitといったツールを使えば、Visual Regression Testにも活用できます。 例えば、前回リリース時点のスクリーンショットと現在のバージョンのスクリーンショットを比較すれば、意図せぬUIの変更が含まれていないか検証できます。
(Andorid スクリーンショットテスト 3つのプロダクトに導入する中で倒してきた課題より引用)
これらのことを実現するために、次のようなワークフローをBitriseを使って整備しました。このワークフローはマージやタグ追加のタイミングで実行されます。
Instrumentation Testを実行し、スクリーンショットを取得する
取得したスクリーンショットの一覧レポートを作成する
任意のバージョン間のスクリーンショットを比較してreg-suitのレポートを作成する
あわせて、不必要な差分が含まれないスクリーンショットを安定して取得できるように基盤の改善をしました。
スクリーンショットテストの導入および、スクリーンショットを安定して取るためにやったことはAndorid スクリーンショットテスト 3つのプロダクトに導入する中で倒してきた課題にもまとめています。是非こちらもご参照ください。
自動テストを書くことを習慣化できたか?
冒頭でSWETのAndroidチームは「社内のAndroidエンジニアが自動テストを書くことを習慣化している」ことをゴールの1つとしていると述べました。 それでは自動テスト導入の取り組みの結果、「GO」の開発チームにおいて自動テストを書くことを習慣化できたのでしょうか。
最近のリリース時点での自動テスト件数とカバレッジの推移は次のようになっています。
まずはLocal Testの推移です。Local Testには主に画面以外のユニットテストが含まれます。
次に、スクリーンショットテストが含まれるInstrumentation Testの推移です。スクリーンショットテストを導入したバージョン5.3.0から集計しています。
ユニットテスト・スクリーンショットテストともに各バージョンで増加しています。また、これらのテストはほとんどSWETメンバーによる実装ではなく、開発メンバー自身が実装したものです。取り組みを始めた当初はユニットテストが十数件ほどでしたが、現在ではコンスタントに自動テストが増えています。
この結果から、「GO」の開発チームにおいては自動テストを書くことを習慣化できたと考えています。
自動テストの習慣化が実現できたのは、開発チームの尽力によるところが大きいです。一方でSWETからも自動テストが定着するような働きかけを行ってきました。
次のセクションでは、自動テストを定着させるためにどのような取り組みを行ってきたかを紹介します。
自動テストがチームに根付くまで
導入したテストが効果を発揮するためには、テストの継続的運用が不可欠です。 そのためには、開発チーム自身がテストの追加・修正といったメンテナンスをし続ける必要があります。
テストを導入したものの、導入した担当者が異動を機にメンテナンスされなくなってしまった
テストを書く気持ちはあるけれども、ついついプロダクトの実装を優先してしまう
といった話は色々なところで耳にします。 開発チームのメンバーひとりひとりがテストを書けるようになり、更にその状態を継続するのは大変なことです。
幸い、「GO」開発チームでは自動テストを書くことを習慣化できました。 自らテストを書く習慣は「こうすれば身に付く」といった答えのあるものではありませんが、一例として「GO」開発チームにおける習慣化のためにSWETが働きかけてきたことを紹介します。
「コード改善 + ユニットテストの導入」で触れたようなテスタビリティ改善の取り組みによって、開発メンバーの書いたテストが少しずつ増えるようになりました。
この段階になってから重要なのがトラブル発生時のサポートです。 特に次のようなトラブルは、一歩間違うと未解決のまま放置されてしまうことになりがちです。
モックライブラリMockkで意図通りにモックできず、テストが失敗してしまう
CI動かしたときだけ、時々テストが失敗してしまう
失敗したテストが残ったままの状態だと「テストをオールグリーンの状態に保つ」という気持ちが折れてしまい、テストがメンテナンスされず放置されるきっかけになってしまいます。 そうなってしまわないように、Slackに常駐して、テスト関連のトラブルに気付いたときには積極的にサポートするようにしました。
現在では、このようなトラブルも開発メンバー自身で解決し、SWETのサポート無しでテストの追加・修正を継続できています。
スクリーンショットテスト
一方スクリーンショットでは、先に触れたようなスクリーンショットテスト基盤やワークフローの整備だけでは、開発チームが自らテストを追加するまでには至りませんでした。 そこからテストを書く習慣が根付くまでの道のりは試行錯誤の連続でした。
モブプロでテストを書くハードルを下げる
当時は、新たに構築したテスト基盤の使い方とテストの書き方の説明会を実施し、その後のテストの追加は開発チームに任せようという考え方でいました。 ところが、説明会終了後もテストが増えないままだったため、テストを書くハードルをもっと下げる必要があるのではないか、と考えました。
そこで、更にテストを書くハードルを下げるために、とある画面のスクリーンショットを撮るテストをモブ・プログラミング(以降モブプロと表記します)で実装してみることにしました。
モブプロの役割分担は次の通りです。
ドライバー(プログラムを書く人):SWET
テスト基盤の使い方などを説明しながらコーディングしました
ナビゲーター(ドライバーに指示する人):開発メンバー
スクリーンショット対象画面を表示するために必要なデータのセットアップ方法を案内しました
このモブプロで、「シンプルなコードで目的の画面のスクリーンショットを撮る」方法が開発チームに伝わったという手応えがありました。
モブプロで開発メンバーにコーディングしてもらう
その後新機能のリリースが立て続けに発生し、長い間テストが追加されない状態でした。
大きなリリースが終わり、開発チームが一息ついた頃に「そろそろスクリーンショットテスト書いてみませんか」と呼び掛けてみたところ、 「前回のモブプロでは自分が手を動かさなかったので書き始められる自信がない」という意見を多数いただきました。
確かに、前回のモブプロでは主にSWETがドライバーをしていたため、開発メンバーがコードを書く機会はほとんど有りませんでした。 そこで、開発メンバーをドライバーに据えて、改めてモブプロを開催することにしました。
ドライバー:開発メンバー1名
ナビゲーター:SWETと、残りの開発メンバー
SWETはテスト基盤の使い方を案内しました
残りの開発メンバーは、狙った画面を表示するための方法を案内しました
このモブプロではスクリーンショット撮影に必要な一通りのコードを全員に書いて欲しかったため、開発メンバー全員がドライバーを担当するまで毎週開催することにしました。 その甲斐あって、スクリーンショットテストが追加されるようになりました!
・・・が、しばらく経つとテストが追加されない状態に戻ってしまいました。
短期的なゴールを共有する
そうこうしている内に、私達が支援できる期限は残り3か月を切ってしまいました。 当初はテストで撮影したスクリーンショットをデザイナーさんにレビューしてもらう計画だったにもかかわらず、肝心のテストが増えないのでレビューしてもらう画像も無いという状況でした。
この状況を打開するため、開発チームに次のような提案をしてみました。
次回リリースのデザインレビューでは、テストで撮ったスクリーンショットをデザイナーさんにレビューしてもらうようにしたい
デザインレビューまでに、次回リリースで追加・修正される画面だけで良いからスクリーンショットを撮れるようにしたい
スクリーンショットテストを1人で書くのはまだ不安があると思うので、引き続きモブプロで書けるだけ書いていきたい
有り難いことに、開発メンバーの皆さんに賛成してもらえたので、3巡目となるモププロを開催しました。 今回のモブプロは役割分担こそ2巡目と同じものの、次の点が異なっていました。
テスト完成の期限が明確になった
スクリーンショットを撮る対象画面の範囲が明確になった
期限と範囲が明確なため、モブプロだけではカバーしきれなかった画面のテストが次々と追加されていきました。 モブプロだけでは必要なスクリーンショットが撮れそうになかったため、デザインレビューの期限に間に合うよう頑張ってもらえたのだと思っています。
また、提案したときは意図していなかったのですが、対象に難易度の高い画面が含まれていた点が良い方向に作用しました。 モブプロで難易度の高い画面に取り組んだことで、意図通りの状態で対象画面(Fragment)を起動するスキルが格段に向上したのです。 合わせて、難易度の高い画面に対応するための改善を、テスト基盤に多数取り込むことができました。
スキルが向上してスクリーンショットテストを書く手間が減っていくと、デザインレビュー以外の方法でも便利に使えるという声が開発メンバーから聞かれるようになりました。
Pull Requestを出すときにスクリーンショットを貼り付けるのが楽になった
複雑な操作をしないと出せない画面を確認するのが楽になった
最終的には、デザインレビュー当日に必要なスクリーンショットテストが全て揃い、色々な画面サイズ・解像度でのスクリーンショットをデザイナーさんに渡すことができました。 開発メンバーの皆さんには、私達が離脱した後も継続してスクリーンショットテストを活用してもらえています。
「GO」のAndroid版にユニットテストとスクリーンショットテストを導入し、開発チームが自動テストを書く習慣が根付くまでに取り組んだ内容を紹介しました。
今回の取り組みを通して、次のような気付きを得ることができました。
ユニットテストのようなシンプルなテストは、最初に参考となるテストを追加することで他のメンバーも書き始めやすくなる
一方で、スクリーンショットテストのような複雑なテストを書いてもらうには、基盤の導入だけでは不十分
テストの書き方を身につけてもらうには、当人がドライバーとなってモブプロを行うのが効果的
目標や期限が決まるとモチベーションがアップする
その一方で、次のような幸運に恵まれた点も多かったと思います。
開発メンバーの皆さんが、自発的に自動テスト実装やアーキテクチャの改善をしてくれたこと
開発メンバーの皆さんが、デバッグが難しい複雑なトラブルでも解決できる高いスキルを持っていたこと
開発メンバーの皆さんがいつも協力的だったこと
私達の支援期間が終了するまでに何とかしたいという気持ちを全員が持っていたこと
他のチームで、ラッキーな面を差し引いたとしてもなおこの方法が通用するのかは分かりませんが、今回の経験で得られた学びはとても大きなものでした。 本記事が、皆さんがテスト導入に取り組むときのヒントになれば幸いです。
Mobility Technologiesの「GO」Android版開発チームの皆さんには、本取り組みのために貴重な時間を割いていただきました。 また、UIテスト基盤のOSS化について快諾していただきました。改めて感謝いたします。
swet-blog 2021-06-08 18:20
Unityプロジェクト向けRoslynアナライザの作りかた
SWETグループの長谷川(@nowsprinting)です。
Unity 2020.2以降、Unityエディタ上でRoslynアナライザによる静的解析 (static analysis) を実行可能になりました。 また、それ以前のバージョンで作られたUnityプロジェクトであっても、JetBrains RiderなどのC#向けIDE(統合開発環境)上でRoslynアナライザの実行がサポートされています。
静的解析を充実させることで、コンパイラだけではチェックしきれないようなバグや性能劣化の原因を早期に検出できます。 例えば弊社では、実行時に動的にインスタンス化されるクラスのコンストラクタがIL2CPPビルド時にストリップされないように [Preserve]アトリビュートの指定漏れを検出するアナライザを導入し、ショーストッパーとなりえる問題を早期発見できるようにしています。
通常、こうした問題の検出はシニアエンジニアによるコードレビューに頼られます。静的解析によってレビュー負荷を軽減できれば、より高度な問題に目を向けられるという副次的効果も見込めます。
本記事では、社内フレームワークやプロジェクト固有のルールに対応するカスタムアナライザを作る手順やTipsを紹介します。
Roslynアナライザとは
Roslyn(ろずりん)とは、C# 6.0から導入された.NETコンパイラプラットフォームの通称です。C#およびVisual Basic向けコンパイラのほか、コード生成API、コード解析APIが公開されています。
Roslynのコード解析APIでは、構文解析 (syntactic analysis) および意味解析 (semantic analysis) を行なうことができ、これを利用して様々なアナライザを自作できます。 これを、本稿ではRoslynアナライザもしくは単にアナライザと呼びます。
なお、一般的なUnityプロジェクトで利用できるオープンソースのアナライザもあり、例えば以下のものが知られています。
Microsoft.Unity.Analyzers
Microsoft.CodeAnalysis.BannedApiAnalyzers
NUnit.Analyzers
UnityプロジェクトでRoslynアナライザを使用する
Roslynアナライザの作り方に先立って、Unityプロジェクトでアナライザを使用する方法について述べます。
Unity 2020.2以降、Unityプロジェクト内のアナライザ(および依存する)DLLファイルを適切に設定することで、Unityエディタ上で静的解析が実行されるようになりました。しかし、実用するにはまだ使い勝手が良いとは言えません *1 *2 *3 *4 *5 *6 *7。
一方で、多くの方がコーディングに使用されているC#向けIDE(具体的にはJetBrains Rider、Visual Studio、Visual Studio Code)では、プロジェクトの.csprojファイルに使用するアナライザを定義してあれば静的解析が実行されます。 アナライザによる診断がもっとも欲しいタイミングは、まさにIDEでコードを書いているときです。アナライザを導入するのであれば、IDEでの診断をメインに据えることをおすすめします。
まずUnityプロジェクトでのDLL設定を、続いて、その設定を.csprojに反映する方法を紹介します。
UnityプロジェクトでのDLL設定
Unityプロジェクト(2019.4で確認しています)に配置したDLLファイルはPlugin Inspectorウィンドウで設定を変更でき、その設定はDLLの.metaファイルに書き込まれます。
Plugin Inspectorウィンドウ
ProjectウィンドウでAssetsフォルダ下にあるアナライザのDLLを選択してPlugin Inspectorウィンドウを表示し、以下のように変更します。
Select platform for plugin下のチェックをすべてoff
Asset Labelsに RoslynAnalyzer を追加 *8
DLLがAssetsフォルダ下にない場合、Asset Labelsは設定できませんので注意してください。
アナライザ設定を.csprojファイルへ反映する
IDEが使用する.csprojファイルは、IDEに対応したプラグインパッケージ *9 によって自動生成されます。 以下のプラグインでは、前述のようにUnity 2020.2向けに設定されたアナライザDLLの設定を.csprojに反映してくれますので、Unityプロジェクト側で前述の設定を済ませるだけでIDEでも静的解析が実行されるようになります。
JetBrains Rider Editor v2.0.6以降
Visual Studio Code Editor *10 v1.2.0以降
上記以外のIDEをお使いの場合、また、AdditionalFilesを使用するアナライザを使用する場合は、.csproj生成時にアナライザの定義を挿入する必要があります。 この設定をサポートしてくれるエディタ拡張がCysharpさんにより公開されていますので、こちらを利用してみてください。
重要度設定とサプレス設定
アナライザには、診断項目ごとにデフォルトの重要度 (severity) が設定されています。プロジェクトによって重要度を上げたい/下げたい場合、これを上書き設定できます。
UnityエディタおよびVisual Studioの場合、ルールセットファイルを使用します。設定方法は公式マニュアルの Roslyn analyzers and ruleset files を参照してください。 Riderの場合は、Preferences... を開き、Editor | Inspection Settings | Roslyn Analyzersの中で設定できます。
また、特定のコードにおいてのみ、診断の対象外にしたいケースもあります。
対象となるクラスやメソッド定義に対して [SuppressMessage]アトリビュートをつけることで、そのクラス/メソッド内では指定した診断をサプレスできます
対象となるコード行の前後に #pragma warning disable   と #pragma warning restore   ディレクティブを書くことで、指定した診断をサプレスできます
なお、Riderにはインスペクションを行単位でサプレスできるコメント書式がありますが、Rider上であってもRoslynアナライザの診断に対しては無効です。 #pragma warning disable/restore ディレクティブを使用してください。
Roslynアナライザの作成(基礎編)
続いて、基本的なRoslynアナライザ作成の手順を紹介します。
Roslynアナライザは、.NETプロジェクトとして作成します。 .NET SDK コマンドラインツールのほか、.NETプロジェクトを扱うことのできるIDEが使用できます。
ただし、アナライザ用のプロジェクトテンプレートは、Windows版のVisual Studioでのみ提供されています。 ここではVisual Studio 2019でプロジェクトを作成する手順を紹介します *11。
Visual Studio Installerを起動し、インストール済みVisual Studio 2019 *12 の「変更」をクリック。「個別のコンポーネント」タブにある「.NET Compiler Platform SDK」を選択してインストールします
Visual Studio 2019を起動し、「新しいプロジェクトの作成」をクリック。プロジェクトテンプレートとして「Analyzer with Code Fix (.NET Standard)」のC#のほうを選択します(同じ名称でVisual Basic向けもあるので注意)
これでアナライザのひな形ができました。 ひな形のソリューションは、例えば名称を HogeFugaAnalyzer としたとき、下記5つのプロジェクト (.csproj) で構成されています。
HogeFugaAnalyzer: アナライザ本体。この名称はソリューションと同じものです
HogeFugaAnalyzer.CodeFixes: アナライザで検出した問題のオートフィックス機能を提供するプロジェクトです。本記事では解説しません
HogeFugaAnalyzer.Package: アナライザ本体とCode FixをNuGetパッケージ (.nupkg) ファイルにパッケージングするプロジェクトです。Unityプロジェクト専用のアナライザではDLLファイルを直接扱うため使用しません
HogeFugaAnalyzer.Test: アナライザのユニットテストを記述するプロジェクトです
HogeFugaAnalyzer.Vsix: Visual Studio拡張機能としてアナライザをデバッグ実行するためのプロジェクトです。本記事では解説しません *13
Windows以外の環境でビルドする設定
以降の開発をWindows以外の環境で行なう場合、ひな形のままではビルドできないため、アナライザ本体の PackageID 属性を書き換えます。
HogeFugaAnalyzer.csprojを任意のエディタで開き、
*$(MSBuildProjectFullPath)*
HogeFugaAnalyzer
に変更します。 ただし、この名称はNuGetパッケージ (.nupkg) の名前としてパッケージングプロジェクト (HogeFugaAnalyzer.Package.csproj) 内で定義されています。同時にHogeFugaAnalyzer.Packageプロジェクトは削除しましょう。
もしNuGetパッケージとして配布を予定しているのであれば、パッケージングにまつわる設定をアナライザ本体の.csprojに記述するか、もしくは本体のPackageIDを例えば下記のように変更することで回避できます。
HogeFugaAnalyzer.Diagnostic
テストプロジェクトの設定
生成されたひな形ではテストプロジェクトが.NET Core App 2.0向けに設定されているため、開発環境に合わせて変更します。
例えば.NET SDK 5.0で開発する場合、 HogeFugaAnalyzer.Test.csprojをエディタで開き、
netcoreapp2.0
netcoreapp5.0
以上で、IDE上でビルドが成功する状態になります。
アナライザのひな形プロジェクトでは、コード上にあらわれる型の名称を検査し、小文字が含まれていれば警告するサンプルが実装されています。 これをもとに、簡単に診断の流れを解説します。
DiagnosticAnalyzer
アナライザは、DiagnosticAnalyzer を継承かつ [DiagnosticAnalyzer]アトリビュートがつけられたクラスとして定義されます。
DiagnosticAnalyzerを継承したクラスには、SupportedDiagnosticsプロパティおよび Initializeメソッドの実装が必要です。
SupportedDiagnostics
このアナライザが提供する診断内容 (DiagnosticDescriptor) を配列で返します。
DiagnosticDescriptor は、IDEのインスペクション設定で一覧表示され、重要度 (severity) を設定させるために使われます。 また、実際に問題を検出した際に表示されるメッセージもここに設定します。
ひな形では private static readonly DiagnosticDescriptor Rule を定義し、これを ImmutableArray でラップして返す実装になっています。
public override ImmutableArray  SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
Roslynコンパイラによってアナライザがロードされると呼ばれるメソッドです。
コンパイラは、ソースコードに対して、字句解析・構文解析・意味解析といったフェーズを経て中間言語 (Intermediate Language) を出力します。 アナライザは、その診断内容に適したフェーズでコンパイラからコールバックを受けて動作します。 Initializeメソッドでは、コールバックを受け取りたい契機を AnalysisContext に登録します。
ひな形では、シンボルの意味解析完了ごとに動作させたいメソッド AnalyzeSymbol() を、 RegisterSymbolAction() で登録しています。
public override void Initialize(AnalysisContext context) { (snip) context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); }
アクションには、シンボルのほかにもコンパイルの開始・終了、メソッド呼び出しなど多数のアクションが定義されており、自由に組合わせてアナライザを作ることができます。
ただし、各アクションの呼び出し順は保証されていません *14。たとえばsymbol actionの時点でsyntax tree actionの処理が完了していることに依存するようなアナライザは動作しない恐れがあります。
アクションに関する詳細は、Roslynのドキュメント Analyzer Actions Semantics を参照してください。
コールバックを受けるよう登録した private static void AnalyzeSymbol() が実際の診断を行なうメソッドです。 診断に必要な情報は引数で受け取れます。ここではシンボルの名前に小文字を含む場合、診断結果である Diagnostic を生成して返しています。
private static void AnalyzeSymbol(SymbolAnalysisContext context) { var namedTypeSymbol = (INamedTypeSymbol)context.Symbol; if (namedTypeSymbol.Name.ToCharArray().Any(char.IsLower)) { var diagnostic = Diagnostic.Create( Rule, namedTypeSymbol.Locations[0], namedTypeSymbol.Name); context.ReportDiagnostic(diagnostic); } }
Diagnostic.Create() の引数は、DiagnosticDescriptor、Location、メッセージ挿入語句となっています。 この結果を受けたIDE上では Location で指示された位置にポップアップ等で DiagnosticDescriptor に定義されたメッセージが表示されます。
Riderでの診断結果表示例
以上のように、アナライザプロジェクトのひな形は、そのままでアナライザとして動作するものが生成されています。 アナライザの開発がはじめてであれば、この時点で一度Unityプロジェクトに組み込んでその振る舞いを確認することをおすすめします。
アナライザ作成においても、できるだけ早い段階かつ小さい単位でユニットテストを行なうことは役に立ちます。 しかし、Roslynコード解析APIで提供されているテストAPIは、解析対象コードと期待する Diagnostic を引数に取って合否検証まで行なうという粒度の大きいものしかありません。 このAPIでは、複雑な診断を行なうアナライザのテストは困難です。
そこで、アナライザの実行と結果検証を分けて使えるフレームワークDena.CodeAnalysis.Testingを作成し、使用しています。 Dena.CodeAnalysis.Testingは、GitHubおよびNuGet Galleryで公開しています。
www.nuget.org
Dena.CodeAnalysis.Testingを使用するには、テストプロジェクトの.csprojに下記の定義を追加します。
Dena.CodeAnalysis.Testingを用いたテストの書きかた
テストは次のように記述できます。
var analyzer = new YourAnalyzer(); var diagnostics = await DiagnosticAnalyzerRunner.Run( analyzer, @" public static class Foo { public static void Bar() { System.Console.WriteLine(""Hello, World!""); } }"); var actual = diagnostics .Where(x => x.Id != "CS1591") // Ignore "Missing XML comment for publicly visible type or member" .ToArray(); Assert.AreEqual(1, actual.Length); Assert.AreEqual("YourAnalyzer0001", actual.First().Id);
この例では DiagnosticAnalyzerRunner.Run にアナライザのインスタンスと検証対象コードを渡し、診断結果を受け取った後に Assert で期待通りの結果であるかを検証しています。
アナライザの実行と Assert が分離されているため、最終的な診断結果だけでなく、アナライザ内の中間情報を検証するテストを書くこともできます。 また、アナライザをテストダブル(モック、スタブ、スパイ等)に置き換えてのテストも可能です。
ユニットテストを考えるとき、例えば INamedTypeSymbol などをテストデータとして生成できればよいのですが、Roslyn APIでは困難です。そのため上記のように診断対象コードをテストの入力データとしています。
最後の Assert はテスト対象や目的に応じて様々な書きかたがありますが、ひとつ注意すべき点があります。 上例では診断結果から CS1591 を取り除いた上で、件数および DIagnostic.Id を検証しています。 もしこれを直接
Assert.IsTrue(diagnostics.Any(x => x.Id == "YourAnalyzer0001"));
のように書いてしまうと、想定外の診断結果バグを見逃してしまったり(上例では2つ目の Diagnostic が無いことを確認できません)、返るはずの Diagnostic が返らなかったとき原因判明に時間がかかったりします(テストデータのミスによるコンパイルエラーは多々発生します)。 少々面倒に感じますが、関係ないものを明示的に取り除いてから Assert する手順を踏むことをおすすめします。
LocationAssert.HaveTheSpan
Dena.CodeAnalysis.Testingでは、補助的なツールとして LocationAssert.HaveTheSpan も提供しています。 次のように、 DiagnosticResult に含まれる Location が正しく設定されていることの検証を簡単に記述できます。
LocationAssert.HaveTheSpan( new LinePosition(7, 34), // 期待する始点 new LinePosition(7, 42), // 期待する終点 diagnostic.Location );
なお、Location の行およびカラムはコードでは0オリジンで指定しますが、診断メッセージには1オリジンで(つまり+1して)表示されます。 1足すか引くかは混乱しがちなポイントですが、 LocationAssert.HaveTheSpan では次のようにアサートメッセージに両方の数値が並べて表示され、判別しやすくなっています。 また、メッセージはそのままテストコードのexpectedとしてコピー&ペーストできる書式となっています。
LocationAssert.HaveTheSpanの差分表示例
テストデータについてのTips
ユニットテストの効率を高めるため、テストで使用するデータ(アナライザにおいては診断対象コード)の質には気を配るべきです。 特に、実際の検証対象コードと乖離したデータでテストを書いてそれがパスしてしまうと、アナライザを実際のプロジェクトへと組み込んでから問題に気づくこととなり手戻りが大きくなります。
そのため、よほどシンプルなものを除き、テストデータは string ではなく個別のテキストファイルに定義することをおすすめします。 拡張子を.csにすることで、コンパイルエラーや警告をあらかじめ修正でき、テスト実行時に自作アナライザとは無関係の診断結果に煩わされることもなくなります。
複数のテストデータで同名のクラスを使用している場合、ファイルを小分けすることで使いまわしができます。逆に同名のクラスをあえて使い分けたい場合、テストケースごとに namespace を使い分けることで実現できます。
その他、以下の点にも注意してみてください。
メソッド呼び出しを検出するとき、それが拡張メソッドでないか。拡張メソッドは明示的に型が異なるため、テストデータも拡張メソッドとして記述しなければ検出できません
アトリビュートのクラス名に Attribute を付け忘れていないか。例えば、アトリビュートのクラス名が HogeAttribute でも Hoge でも、[Hoge] と記述できてしまいます。しかし、アナライザから見ると別物です
Unityプロジェクトに組み込んでのテストTips
アナライザをDLLにビルドし、それをUnityプロジェクトに組み込んでテストする際のTipsを紹介します。
極力ユニットテストで品質を上げておく
Unityプロジェクトに組み込んでからのテストではデバッグしづらく、また修正・再実行に時間もかかります。 大前提として、極力、ユニットテストでリアリティの高いテストデータ(診断対象コード)を使って品質を上げておくべきです。
dotnet buildコマンドを使用する
IDEでアナライザを実行する場合、IDEによってアナライザ自体がキャッシュされるため、何度も安定したテストを実行することは困難です。 そのため、初期の段階ではdotnetコマンドでインクリメンタルコンパイルを無効にした実行を試すことをおすすめします。
次のコマンドで実行できます。
$ dotnet build --no-incremental YOUR_PROJECT_NAME.csproj
ファイルロガーを使用する
アナライザをUnityプロジェクトに組み込むと、コンソール出力を観測できないため、いわゆるprintデバッグは不可能になります。 そのため、デバッグ困難な事象に突き当たってしまったら、早めにファイルロガーの仕組みを入れることをおすすめします。
なお、OSSなど外部のロガーソリューションを利用する場合、依存するDLLもすべてアナライザ本体同様   ノードに書く必要がある点に注意してください。
Roslynアナライザの作成(応用編)
実用的なアナライザとして、冒頭で触れた「実行時に動的にインスタンス化されるクラスのコンストラクタがIL2CPPビルド時にストリップされないように [Preserve] アトリビュートの指定漏れを検出するアナライザ」の作成方法を紹介します。
このアナライザの実現には、二段階の処理が必要です。 まず、メソッドの呼び出し箇所でコールバックを受け、それが特定のメソッド呼び出しであるかを判断し型引数を取得します。 続いて、その型のコンストラクタに [Preserve] アトリビュートが定義されてるかを診断します *15。
では、順に見ていきましょう。まず Initialize でメソッド呼び出し箇所のコールバックを登録します。
public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterOperationAction( AnalyzeAttributes, OperationKind.Invocation ); }
コールバックに渡される OperationAnalysisContext からは、呼び出しているメソッドの情報が取得できます。 ここでは、クラスを型引数として取り、実行時に動的にインスタンス化するクラスを登録しているメソッド呼び出しを検出します。
private void AnalyzeAttributes(OperationAnalysisContext context) { var invocation = (IInvocationOperation)context.Operation; var methodSymbol = invocation.TargetMethod; if (methodSymbol.ContainingType.Name != "ServiceCollection") return; if (methodSymbol.Name != "AddSingleton") return; if (methodSymbol.TypeArguments.Count()==0) return;
特定のメソッド呼び出し(ここでは ServiceCollection.AddSingleton ())であったならば、続いて型引数のコンストラクタについているアトリビュートを診断します。
var injectedClass = methodSymbol.TypeArguments[0] as INamedTypeSymbol; var ctorShouldBePreserved = new List (); foreach (var ctor in injectedClass.Constructors) { foreach (var attribute in ctor.GetAttributes()) { if (attribute.AttributeClass?.Name == "PreserveAttribute") { ctorShouldBePreserved.Add(ctor); } } }
以上で、ServiceCollection.AddSingleton () に型引数として渡しているクラスのコンストラクタに [Preserve] アトリビュートがついているか否かを確認できました。
最後に、診断結果をコンパイラに渡します。
if (ctorShouldBePreserved.Count == 0) { var diagnostic = Diagnostic.Create( CtorDoesNotHaveInject, // [Preserve]付きコンストラクタが存在しない意のDiagnosticDescriptor location, // 割愛していますが、呼び出し部分の型引数部分を指すLocation injectedClass.Name); context.ReportDiagnostic(diagnostic); } }
コードでは割愛していますが、この診断結果の Location (primary location) には、本来の問題箇所である型引数のコンストラクタ定義箇所ではなく ServiceCollection.AddSingleton () 呼び出し位置(の型引数部分)を指定しています。
IDE上でアナライザが実行されるときはエディタタブで開かれているファイルを起点とした診断しか行われないため *16 *17、このアナライザは呼び出し側のファイルでしか機能せず、またそのファイル以外を指す診断結果は無効となるためです。
Preserveアトリビュートの指定漏れを検出するアナライザの診断例
なかなか情報も少なく、ハードルが高い印象のRoslynアナライザです。しかし、導入してしまえばエンジニアへの負担なく問題を早期発見できる、とても効果の高いものです。
プロジェクトのコーディング規約やレビューでの観点のうち明文化されているものがあれば、それをアナライザにはできないか、検討してみてはいかがでしょうか。
最後に。以上のような活動に共感していただけた方、興味を持たれた方、一緒に働いてみようと思ってくれた方。 下記職種で採用しておりますので、ぜひご応募ください。お待ちしております。
career.dena.jp
*1:診断結果は、GUIではコンソールウィンドウ、CLI (Batch mode) ではビルドログに混ざって出力されます
*2:Unity 2020.2では、診断は当該ファイルのコンパイルもしくはReimportの契機でのみ実行されます(Unity 2020.3.4より、通常のコンパイルステップで動作するように修正されました)
*3:Unity 2020.2では、ルールセットファイルによる重要度の設定変更は、Reimportの契機でのみ反映されます(Unity 2021.1で修正されました)
*4:Unity 2020.2では、CLI実行ではルールセットファイルによる重要度設定は無効です(Unity 2021.1で修正されました)
*5:Packages/下のDLLには、アナライザとして識別させるためのラベル設定ができません
*6:Packages/下にアナライザとして設定したDLL(と.metaファイル)を配置しても、アナライザとして動作しません
*7:その他、Unity 2020.2時点のRoslynアナライザサポート状況はこちらにまとめてあります https://www.nowsprinting.com/entry/2021/04/18/200619
*8:ラベルは、ウィンドウ右下のしおり状アイコンをクリックすることで入力できます。アイコンが表示されていない場合は"Asset Labels"の文字をクリックすると表示されます
*9:Package Managerウィンドウでインポートおよびアップデートできます
*10:Visual Studio Code (VSCode) 用プラグインです。Visual Studio用ではないのでご注意ください
*11:その他の環境でアナライザプロジェクトを作成する場合は、 Roslyn SDK内にあるテンプレート https://github.com/dotnet/roslyn-sdk/tree/main/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic もしくはテンプレートリポジトリ https://github.com/nowsprinting/RoslynAnalyzerTemplate を利用するか、こちらの記事を参考に設定してください https://zenn.dev/naminodarie/articles/32973a36fcbe99
*12:Community EditionでもRoslynアナライザは作成できますが、for macではできません
*13:Visual Studio 2019 v16.10ではデバッグ実行の手段が変わるため不要になるようです。参考 https://qiita.com/ryuix/items/36dabbf3c7e4e395e49e
*14:compilation start/endのような対になっているものの呼び出し順は保証されます。また、compilation end actionは必ず最後に1回実行されます
*15:あからじめSymbolActionでコンストラクタのアトリビュートを収集しておき、後でメソッド呼び出し箇所のコールバックで処理する、という二段構えの方法も考えられますが、2つの理由で採用していません。1つ目は、アクションの呼び出し順が未定義であること。2つ目は、IDE上で実行されるときはエディタタブで開かれているファイルを起点とした診断しか行われないため、型引数に使われている型が別ファイルにあるとSymbolActionが呼ばれないためです
*16:インスペクション機能はプロジェクト全体を診断してくれますが、この場合も実行単位はファイルごとのため同様の制限があります
*17:先に紹介したdotnet buildコマンドではこの制限を受けないため、診断されているはずなのにIDEに表示されないときはdotnet buildコマンドを試してみると原因にたどり着きやすいこともあります
swet-blog 2021-05-25 10:00
Jenkins Shared Librariesの活用事例の紹介
SWETグループの井口です(@hisa9chi)です。現在はスマホ向けゲーム開発案件にてゲーム開発者がゲーム開発に集中できるようにCI/CD関連を幅広くサポートしています。 本稿では、その中でも Jenkins Pipeline Job で利用可能な Shared Libraries に関して弊社でどのように活用しているか事例を紹介してみたいと思います。
Jenkinsと聞くとおそらく皆さんは、昔は利用していたが今は運用コストが高いなどの理由から、マネージドなクラウドのCI/CDサービスへ移行したという方が多いのではないでしょうか。しかし、ゲーム開発の現場ではJenkins master / agentのクラスタ構成を構築して、運用を続けているプロジェクトが弊社内にも多く存在します。なぜ、運用コストが高いにもかかわらず構築して運用しているかというと、ゲーム開発特有の理由からです。
ゲーム開発において、修正後にビルドして実機で動作やレイアウトなどを確認するサイクルはとても重要です。このサイクルが短ければ短いほどゲームの修正確認がスムーズに行え、ゲーム開発者はゲーム開発に集中できると思います。しかし、ゲーム開発では大容量のデータを扱うことが多く、開発が進むにつれてビルド時間が増加する傾向にあります。そのため、ビルド時間を少しでも短くするために、高スペックな物理マシンを手元に用意してJenkins master / agentのクラスタ構成を構築して運用しています。
この運用コストに関しては本稿では触れませんが、SWETとして取り組んでいることをCEDEC 2020にて発表しております。発表資料は以下にありますので、そちらも参考にしていただければ幸いです。
CEDEC 2020「モバイルゲーム開発におけるJenkinsクラウド時代のJenkins構築と管理テクニック」
本稿では以下を前提としております。
Jenkins masterのversionは2.x系
実際の動作を確認したのは ver.2.222.3
Jenkins Pipeline JobはDeclarative Pipeline
Scripted Pipelineではないので注意
3. Jenkins Shared Libraries
Jenkinsのジョブ作成にてPipeline Jobが多く利用されるようになると、共通処理が複数のPipeline Jobで出現することがあります。この共通処理をPipeline Jobとは切り離して定義できれば、Pipeline Jobのメンテナンス性向上や共通処理等の再利用性も向上します。この共通処理等をPipeline Jobの外部に定義して、Pipeline Jobごとにロードして利用する仕組みが、Shared Librariesです。
Shared Librariesの特徴として以下があります。
ジョブ毎に異なるバージョンの利用が可能
ライブラリをSCM管理しているため、ブランチ/タグ/コミットハッシュ指定でジョブごとに異なるバージョンのライブラリをロードして利用可能です。
メンテナンス性と再利用性の向上
共通処理等をPipeline Jobの外に定義しているため、変更はライブラリ内に閉じることが可能です。また、Jenkins masterが異なる場合でも、ライブラリが管理されているリポジトリへアクセス可能であれば容易に利用可能です。他にも、Pluginの利用をラップしたライブラリを作成しておくことで、Pluginの更新に伴う変更もライブラリ内に閉じることが可能な場合もあります。
classメソッドのapproveが不要
Pipeline Job内でgroovyを用いてclassメソッド1を利用した処理を定義していた場合、そのclassメソッド毎にapproveが必要となる場合があります。Jenksinfile内のgroovyコードで多くのclassメソッドを利用していてapproveされていない場合、このapprove作業は非常に面倒な作業となります。しかし、Shared Librariesであればapproveする必要がないためそのような面倒な作業は発生しません。
基本的にはライブラリ群を決められたディレクトリ構造で作成して、そのリポジトリをSCM管理します。そのリポジトリをJenkins側に設定してジョブごとでライブラリを読み込むように設定することで、ジョブごとのワークスペースにライブラリ群がチェックアウトされて利用可能になります。
先に示した公式のドキュメントに詳細は記載されていますが、簡潔に説明するとディレクトリ構造として大きく以下となります。
パッケージに分けて独自のクラスを定義可能
pipeline jobで利用可能な変数定義(.groovy)とヘルプファイル(.txt)
xxx.groovyのファイル名を変数として利用可能でありそのファイル内に定義されているメソッドを {ファイル名}.{メソッド名} で呼び出し可能
groovyではないファイル(xxx.jsonやxxx.sh等)を格納
libraryResourceを利用してメソッド内で利用が可能
src/var/resourcesに関しての実装サンプルは公式のサンプルがわかりやすいのでそちらを確認してください。
作成したShared Librariesを利用する場合は以下の2ヵ所に設定が必要となります。
Jenkinsのシステム設定
ジョブごとにライブラリをインポート
3.4.1. Jenkinsのシステム設定
Pipeline Jobにてロード可能なライブラリをGlobal Pipeline Librariesへ設定します。JenkinsからSCM経由でライブラリにアクセス可能であることが前提となります。
設定箇所は以下のように 「Jenkinsの管理」-「システムの設定」内の Global Pipeline Libraries の項目になります。
ライブラリ名。ジョブでインポートする際に利用
Default version
ブランチ名、タグやコミットハッシュを指定
Retrieval method
Source Code Management
利用するSCMサービス
プロジェクトリポジトリ
SSH or HTTPSアクセス用URL
リポジトリへの認証情報
credentialsに登録済みの物を指定
3.4.2. ジョブごとにライブラリをインポート
基本的には、Pipeline Job内で @Library を活用して先ほど設定したライブラリを読み込みます。
// ライブラリの読み込み // デフォルトバージョンの読み込み // "Global Pipeline Libraries 設定" の "Default version" @Library( 'ライブラリ名' ) _ // ブランチやタグを指定する場合 @Library( 'ライブラリ名@{branch名 | tag名}' ) _ // 複数ライブラリの読み込み @Library( ['ライブラリ名1', 'ライブラリ名2'] ) _
注意が必要なのは最後の _ を忘れないことです。これがなければエラーとなるので注意してください。 _ を記載することでライブラリ側に定義した、var 配下のスクリプトが利用可能になます。利用には var 配下のファイル名をそのまま変数名として利用できます。
また、src などで独自に定義したclassの利用に関してはそのクラスをimportする必要があります。
// srcで定義した class を利用する場合 // 利用したい class を import する必要あり @Library( 'ライブラリ名' ) import org.foo.Sample // 利用時は script { } ブロック内で利用する script { def sample = new org.foo.Sample() sample.hello() // Sample 内に定義されている hello() メソッドの呼び出し }
srcで定義されているclassを利用する場合は script {} ブロックに入れる必要があることに注意してください。
では、実際に弊社でどのように利用しているか実際の以下の3つの事例を紹介させていただきます。
成果物等のGCSアップロード
ジョブ終了時に自動メンション(Slack通知)
ジョブ失敗時のstage名の取得
4.1. 成果物等のGCSアップロード
ジョブを実行した際の成果物やログなどはarchiveArtifacts Pluginを活用してJenkins masterに保存しておくことが一般的かと思います。しかし、ジョブの成果物はサイズが大きい物などもありJenkins masterのディスク容量を圧迫してしまいます。この対策として、ジョブごとにビルド履歴の保存件数を制限するbuildDiscarderを設定する方法があります。ただし、以下の問題があります。
ジョブ数が多い場合は結果ディスクフルになってしまいジョブが正しく動作しない
ビルド履歴を制限すると過去の成果物の取得ができない
これらを解決する施策として、クラウドのストレージであるGCSやS3にアップロードして保存しておくことが挙げられます。今回はGCSへのアップロードを例にShared Libraryの利用事例をご紹介します。
GCSへのアップロードにはGoogle Cloud Storage Pluginを利用することで簡単に行えます。ただ、アップロードの際には対象のバケット配下に {ジョブ名}/{ビルド番号} のディレクトリを作成してアップロードしたいという要望があります。Pluginをそのまま活用するとアップロード先にバケット名だけでなく {バケット名}/{ジョブ名}/{ビルド番号} とジョブ毎に指定する必要があります。バケット名に関してはジョブ毎に変更する可能性はありますが、ジョブ名やビルド番号は共通してJenkinsの環境変数から取得して指定します。この共通部分をジョブごとに記載するのは煩わしいためライブラリ化しています。
// var/gUploadArtifactsToGCS.groovy #!/usr/bin/env groovy // Google Cloud Storage Uplaod(for Google Cloud Storage Plugin) def call( Map params = [:] ) { // 必須パラメータチェック if ( params.bucket == null ) { println "gUploadArtifactsToGCS: 'bucket' param is not specified." return } if ( params.credentialsId == null ) { println "gUploadArtifactsToGCS: 'credentialsId' param is not specified." return } if ( params.pattern == null ) { println "gUploadArtifactsToGCS: 'pattern' param is not specified." return } def uploadDir = "gs://${params.bucket}/${env.JOB_NAME}/${env.BUILD_NUMBER}" // Google Cloud Storage Plugin の提供メソッド googleStorageUpload( bucket: uploadDir, credentialsId: params.credentialsId, pattern: params.pattern ) }
// Jenkinsfile @Library( 'mylibrary' ) _ pipeline { agent { label 'master' } stages { stage ( 'sample' ) { steps { sh 'touch sample.txt' gUploadArtifactsToGCS( bucket: ARTIFACTS_BUCKET_NAME, credentialsId: GCS_CREDENTIAL_ID, pattern: 'sample.txt' ) } } } }
上記のようにShared Libraryを呼び出すことで指定したバケット ARTIFACTS_BUCKET_NAME 配下に {ジョブ名}/{ビルド番号}/sample.txt としてアップロードされます。
他にも以下のような情報をGCSへアップロードするようなライブラリを作成して利用しています。
ジョブ実行時に指定したビルドパラメータとビルドログ
Jenkinsにて定義済みのclassを活用してファイルに出力してGCSへアップロード
ジョブを実行したビルドマシンの環境情報
agentのOSやツールのバージョンを取得するスクリプトをShared Library側に登録してそれを呼び出して情報を収集してGCSへアップロード
4.2. ジョブ終了時に自動メンション(Slack通知)
ジョブが終了した際に結果をSlackなどへ通知していることは多いかと思います。弊社でもJenkinsのジョブを手動でトリガーした場合やcronで実行された際の結果をSlackへ通知(Slack Notification Pluginを利用しています)しています。しかし、ジョブも多く頻繁に実行されると通知の数が増えるため、Slackの通知が埋もれてしまい気づけない状況が発生するため、特定の人へメンションしたいというケースがあります。
あるプロジェクトでは、ジョブ毎にメンション先をビルドパラメータ化して実行時に設定してもらうという運用がなされていました。しかし、この方法は利用側からすると面倒であり指定を間違えてしまうこともあります。そのため、ジョブをトリガーしたユーザの情報から自動でそのユーザにメンション付きでSlack通知するようにライブラリを作成して利用しています。
仕組みを簡単に説明すると、トリガーしたユーザのメールアドレス(Slackに登録しているアドレスと同一)を元に、SlackのIDを検索してそのユーザへメンションするための文字列である   を返却しています。2
// var/gGetSlackUsersMentionString.groovy import java.util.concurrent.TimeUnit import groovy.json.* @Grab( group='com.squareup.okhttp', module='okhttp', version='2.7.5' ) import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.Response; def call( Map params = [:] ) { // 必須パラメータチェック if ( params.userEmails == null ) { println "gGetSlackUsersMentionString: 'userEmails' param is not specified." return } // 必須パラメータチェック if ( params.slackAPIToken == null ) { println "gGetSlackUsersMentionString: 'slackAPIToken' param is not specified." return } String slackApiUrl = 'https://slack.com/api/' String apiMethod = 'users.list' String query = 'token=' + params.slackAPIToken; String requestUrl = slackApiUrl + apiMethod + '?' + query Request request = new Request.Builder() .url( requestUrl ) .header( 'User-Agent', 'jenkins' ) .get() .build(); OkHttpClient client = new OkHttpClient(); client.setConnectTimeout( 5, TimeUnit.MINUTES ); // connect timeout client.setReadTimeout( 5, TimeUnit.MINUTES ); // socket timeout // Response Response response = client.newCall( request ).execute(); // json へ変換 Object json = new JsonSlurper().parseText( response.body().string() ) // User の mention 用 ID 文字列 String mentionIdsStr = ''; // email から User ID を検出 for ( user in json.members ) { for ( target in params.userEmails ) { if ( user.profile.email == target ) { mentionIdsStr += '  ' } } } return mentionIdsStr; }
// Jenkinsfile @Library( 'mylibrary' ) _ pipeline { agent { label 'master' } stages { stage ( 'sample' ) { steps { script { def mentionStr = gGetSlackUsersMentionString( userEmails: [ EMAIL_ADDRESS ], slackAPIToken: SLACK_API_TOKEN ) // Slack Notification Plugin の提供メソッド slackSend( color: 'good', message: "${mentionStr}\n Message from Jenkins Pipeline" ) } } } } }
上記のように利用することで、ジョブをトリガーしたユーザへのメンション付きでSlack通知することを可能にしています。
4.3. ジョブ失敗時のstage名の取得
こちらもSlackに通知する際の便利機能の1つになります。Jenkins Pipeline Jobでは複数のstageが定義されています。ジョブが失敗した際のSlack通知にて、どのstageで失敗したかも補足情報として通知されているとエラー箇所の確認などに役立ちます。しかし、エラーとなったstageを特定するのは以下のように面倒であり、Jenkinsfileへのコード量も増えてしまいます。
// Jenkinsfile pipeline { agent { label 'master' } stages { stage( 'sample' ) { .... } post { // 失敗となった場合に stage 名称を環境変数に設定 failure { script { env.ERROR_STAGE='sample' } } // ここまでがエラー stage 名称の設定 } } // Declarative: Post Actions post { failure { // Slack Notification Plugin の提供メソッド slackSend( color: 'danger', message: "ErrorStage: ${env.ERROR_STAGE}" ) } } }
上記のように全てのstageの post { failure { script {} } } ブロックにて env.ERROR_STAGE='ステージ名' というような設定を記載する必要があります。これはかなり面倒であり、多くのメンバーでメンテナンスをしていると記載を忘れてしまうこともあります。 そのため、最後の Declarative: Post Actions でfailureの際にエラーとなったstageを取得できれば余計なコードもなくなりスマートになります。Pipeline Jobの環境変数などでエラーstage情報が提供されていないため、Jenkinsのクラスからstageを取得する以下のライブラリを作成して利用しています。
// vars/gGetFailedStageName.groovy #!/usr/bin/env groovy import hudson.model.Run; import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.jenkinsci.plugins.workflow.graphanalysis.ForkScanner; import org.jenkinsci.plugins.workflow.pipelinegraphanalysis.StageChunkFinder; import com.cloudbees.workflow.rest.external.StageNodeExt; import com.cloudbees.workflow.rest.external.StatusExt; import com.cloudbees.workflow.rest.external.ChunkVisitor; def call() { String errorStageName = ''; WorkflowRun workFlowRun = currentBuild.getRawBuild(); ChunkVisitor visitor = new ChunkVisitor( workFlowRun ); // stage 情報の取得 ForkScanner.visitSimpleChunks( workFlowRun.getExecution().getCurrentHeads(), visitor, new StageChunkFinder() ); // 全ての Stage のステータスを検索 for ( StageNodeExt stageExt : visitor.getStages() ) { if ( stageExt.getStatus() == StatusExt.FAILED ) { errorStageName = stageExt.getName(); break; } } return errorStageName; }
// Jenkinsfile @Library( 'mylibrary' ) _ pipeline { agent { label 'master' } stages { stage ( 'sample' ) { .... error '強制的にエラー' // 以降の stage はスキップ stage のステータスはエラー状態 } } // Declarative: Post Actions post { failure { // Slack Notification Plugin の提供メソッド slackSend( color: 'danger', message: "ErrorStage: ${gGetFailedStageName()}" ) } } }
上記のように利用することでエラーとなったstage名をSlackのメッセージ内に入れることが可能になります。
ただし、先頭から確認して最初にエラーとなったstage名のみ(上記のサンプルでは "sample" stage名のみ)を返却するようになっています。基本的には最初にエラーとなったstage以降のstageはスキップされ失敗した状態となっています。そのため、最初にエラーとなったstage名だけを返却する方針で良いと判断したためこのような仕組みとなっております。
今回は弊社で利用しているJenkins Piepline JobにおけるShared Librariesの利用事例を紹介しました。今回紹介したのは一部であり、Shared Librariesは独自クラスを定義してJenkins Pipeline Jobで利用が可能になるなど他にも多くの利用方法が挙げられます。Shared Librariesに関しては私たちも日々どのように利用するのが良いか色々と模索しています。ですので、自分たちも利用しているという方がおられましたら、これを機会にどのように活用しているかなどのノウハウや情報をお互いに交換できればと思っています。 もし、ざっくばらんに話してみたいなどありましたら井口(@hisa9chi)までご連絡いただければと思います。
例えばユーザID情報を取得のメソッドであるhudson.model.Cause$UserIdCause getUserId()など↩
Shared Libraryを使ってSlackのUserIDを取得していますが、現在ではPluginでemailからSlackのUserIdを検索できるようになっています。slackUserIdFromEmail↩
swet-blog 2021-01-18 20:00
大規模リポジトリで高速にgit cloneするテクニック
ニッチな話題ですが、業務におけるCI/CDの現場では避けることのできない大規模リポジトリと戦うためのgit cloneのテクニックを紹介します。
この記事はDeNA Advent Calendar 2020の10日目の記事です。
CI/CDマニアの@Kesin11です。SWETではCI/CDチームの一員として、CI/CDの啓蒙活動やJenkinsを必要とするチームのサポートなどの業務を行っています。
おそらくどこの会社でも1つぐらいは巨大なリポジトリが存在しているかと思いますが、歴史あるリポジトリはgit cloneするだけで数分を要し、checkout後のリポジトリサイズがGB単位になることも珍しくないでしょう。業務で古くから存在するプロジェクトのリポジトリを触ったことがある方はきっと経験があるかと思います。
git cloneを実行するのは最初のセットアップ時だけなのであまり問題にならないと思われるかもしれませんが、実は1日に何度もgit cloneを行う環境もあります。そう、CI/CDの環境ですね。
CircleCIやGitHub Actionsといった一般的なCIサービスでは、毎回まっさらな環境にリポジトリをgit cloneするところからスタートします。巨大リポジトリではこの1ステップ目のgit cloneだけで数分かかってしまうため、単にLintを行うだけのジョブであってもすぐに結果が返ってこないという状況になります。
gitでは仮にファイルを削除しても昔のコミットから復元可能です。つまり、過去全ての情報が保存されているためリポジトリは歴史と共に太っていく一方であり、改善の見込みもありません1。
ところで、git cloneは知らない人がいないほどポピュラーなコマンドですが、実はオプションが豊富に存在することはご存知でしょうか?手元でman git-cloneを実行してみください。きっと知らないオプションがたくさんあり驚くと思います。オプションの中には不要な過去のコミット、ブランチ、ファイルなどを取得しないことでgit cloneを効率的にできるものがありますので、今回はこれらを活用するテクニックをお伝えします。
git cloneの各種オプション
この後に各種オプションを駆使したgit cloneを紹介するのですが、まずは比較のために何もオプションを付けないときの実行時間と、checkout後のリポジトリのサイズを計測しておきます。実験に使用するリポジトリには、歴史がとても長いgit/gitを採用しました。
$ time git clone git@github.com:git/git.git Cloning into 'git'... remote: Enumerating objects: 279, done. remote: Counting objects: 100% (279/279), done. remote: Compressing objects: 100% (99/99), done. remote: Total 298430 (delta 193), reused 248 (delta 180), pack-reused 298151 Receiving objects: 100% (298430/298430), 148.71 MiB | 10.06 MiB/s, done. Resolving deltas: 100% (222617/222617), done. Updating files: 100% (3866/3866), done. real 0m27.057s user 0m14.959s sys 0m3.038s $ du -sh git git/.git 214M git 169M git/.git
時間についてはインターネット回線、時間帯などに依存するためあくまで参考程度にしてください。時間に加えてcheckout後のリポジトリそのものと、昔のコミットやgitのオブジェクトなどの情報を全て格納している.gitだけのサイズを計測しています。
これを基準とし、どこまで改善できるのかを見ていきましょう。
まずは知ってる人も多いであろうdepthです。depthを指定すると、その数のコミットの歴史だけに限定したデータを取得します。これをshallow cloneと呼ぶことがあり、gitやCIサービスのドキュメントなどにも頻出しますので、shallow = depthなどのオプションによって歴史を省略したcheckoutと覚えておくとよいです。
過去のコミット分の情報が不要ということは、昔のファイルの状態を記録しているGitオブジェクトが軽くて済むということです。2
ではどれほど効果があるのか試してみましょう。
$ time git clone --depth=1 git@github.com:git/git.git git_depth1 Cloning into 'git_depth1'... remote: Enumerating objects: 3960, done. remote: Counting objects: 100% (3960/3960), done. remote: Compressing objects: 100% (3506/3506), done. remote: Total 3960 (delta 356), reused 1917 (delta 287), pack-reused 0 Receiving objects: 100% (3960/3960), 9.30 MiB | 7.25 MiB/s, done. Resolving deltas: 100% (356/356), done. Updating files: 100% (3866/3866), done. real 0m11.401s user 0m0.466s sys 0m0.862s $ du -sh git_depth1/ git_depth1/.git 56M git_depth1/ 11M git_depth1/.git
(繰り返しになりますが、git cloneにかかる時間自体は様々な要因によって安定しない可能性が高いので、あくまで参考にしてください)
--depth=1を指定すると、指定したブランチ(指定しない場合はデフォルトブランチ)の最後のコミットだけを取得します。オプションを何も付けなかった場合と比較して、時間は1/2以下、リポジトリの容量は約1/4で.gitだけに限定すれば約1/15程度になりました。時間に関しては誤差があるとはいえかなり改善できました。
代わりに、depth付きでgit cloneしたリポジトリではgit logをしても過去のコミットを見ることはできませんし、昔のコードをcheckoutもできません。とりあえず最新のリポジトリの状態でビルドしたいというCIなどの用途においては過去のコミットの情報は不要なので、メリットだけを享受できるでしょう。
single-branch
git cloneについて調べるとdepthの次に目にすることが多いのはsingle-branchではないでしょうか。その名の通り、単一のブランチだけの情報を取得するので通常よりは取得するデータ量が減ります。しかし、depthとは異なり指定したブランチの歴史は全て取得するので、削減量としてはdepthには及ばないはずです。実際に試してみましょう。
$ time git clone -b master --single-branch git@github.com:git/git.git git_single_branch Cloning into 'git_single_branch'... remote: Enumerating objects: 832, done. remote: Counting objects: 100% (826/826), done. remote: Compressing objects: 100% (804/804), done. remote: Total 289165 (delta 28), reused 814 (delta 22), pack-reused 288339 Receiving objects: 100% (289165/289165), 143.57 MiB | 9.93 MiB/s, done. Resolving deltas: 100% (216511/216511), done. Updating files: 100% (3866/3866), done. real 0m25.670s user 0m13.348s sys 0m2.899s $ du -sh git_single_branch/ git_single_branch/.git 201M git_single_branch/ 155M git_single_branch/.git
期待通りの結果になりました。通常よりは多少軽くなりましたが、depth=1には及びません。single-branchを使う場合の注意点としては、今後fetchやpullをした場合でもsingle-branchで指定したブランチ以外のリモートブランチは取得されないことです。つまり、pull-requestが出ているブランチをcheckoutしてエディタ上でコードレビューする、みたいな使い方はできなくなります。
この挙動になる理由はfetchのrefspecを見てもらうと分かります。各オプションでgit cloneしてきたリポジトリで比較してみます。
$ git config --get remote.origin.fetch # 通常 +refs/heads/*:refs/remotes/origin/* # depth=1 +refs/heads/master:refs/remotes/origin/master # -b master --single-branch +refs/heads/master:refs/remotes/origin/master
refspecを解説するとそれだけで1つ記事が書けてしまうので今回は省略しますが、depthもsingle-branchのどちらもこのrefspecによって取得するブランチをmasterだけに限定していることが高速化3のポイントです。refspecについてもっと深く知りたい方はドキュメントを見てみましょう。
補足:depth=1とsingle-branchの併用について
git cloneの高速化でよく目にする例として、git clone --depth=1 -b master --single-branchのように2つのオプションを併用するものですが、これは高速化の観点では意味はないはずです。なぜならdepthを指定している時点で取得するコミットの歴史は制限されていますし、先ほど示したようにrefspecも特定のブランチに限定されているためです。
shallow-since
これは少しマイナーなオプションですがdepthの日付版だと思ってください。depthではコミットの数で限定しましたが、shallow-sinceはある時点以降のコミットに限定して取得できます。CIの用途ではdepthで十分だと思いますが、例えば歴史が長いリポジトリにおいてある時期に全体的に書き換えてv2にしたのでもう昔のv1時代のコミットはgit logで見ることもない、というケースなどで自分のマシンにcloneする場合は便利かもしれません。
$ time git clone --shallow-since="2020/01/01" git@github.com:git/git.git git_shallow_since1 Cloning into 'git_shallow_since1'... remote: Enumerating objects: 27410, done. remote: Counting objects: 100% (27410/27410), done. remote: Compressing objects: 100% (12816/12816), done. remote: Total 27410 (delta 19562), reused 20870 (delta 14326), pack-reused 0 Receiving objects: 100% (27410/27410), 26.83 MiB | 9.33 MiB/s, done. Resolving deltas: 100% (19562/19562), done. Updating files: 100% (3866/3866), done. real 0m17.672s user 0m2.304s sys 0m1.225s $ du -sh git_shallow_since1/ git_shallow_since1/.git 74M git_shallow_since1/ 28M git_shallow_since1/.git
本当に2020/01/01以降のコミットだけを取得しているのか調べてみましょう。結果に1つだけ2019-12-31が含まれていますが、おそらくタイムゾーン周りをあまり厳密に考慮しなかったからだと思われます。
$ git log --format="%H %as" | tail 0d2116c6441079a5a1091e4cf152fd9d5fa9811b 2020-01-05 9d48668cd51220f1d8b83c7e1aa65416520aeee6 2020-01-04 3a05aacddd8e3d65ba7988dc6e4f8a88bc4e3320 2020-01-04 4c5081614c0fa5a9060ae294cc4364f990254f04 2020-01-03 5bb457409c15d221ee38240f83362b3c6fd96421 2020-01-03 63020f175fe26f3250ac8d19d02ef9ee271006e5 2020-01-02 224c7d70fa14ed44d8e7e3ce1e165e05b7b23725 2019-12-31 9c8a294a1ae1335511475db9c0eb8841c0ec9738 2020-01-02 763a59e71cee1542665e640f63141a0bf89e6381 2020-01-01 44143583b76decf93c55b73adaf2367c22c88998 2019-12-31
sparse-checkout
ここまでは過去のコミット、つまり時間方向へのデータ量削減でした。別の方向としてcheckoutするファイルを限定するということも可能です。
sparse-checkoutによって、巨大なリポジトリの中から必要なファイルとそれに関係するコミットだけを取得できます。例えば、複数のプロジェクトが1つのリポジトリに同居するmonorepo構成の場合に、自分の作業に関係するプロジェクトだけをcheckoutできれば十分というケースで重宝します。
これを実現するためのgit sparse-checkoutというコマンドがv2.25.0から追加されたようなのですが、v2.29.0時点ではまだEXPERIMENTALとのことなのでここでは昔ながらの方法を使います。
# 自前でディレクトリを作って空のリポジトリを作るところからスタート $ mkdir git_sparse $ cd git_sparse $ git init $ git remote add origin git@github.com:git/git.git # git/gitのDocumentationディレクトリのみをcheckoutするように設定 $ git config core.sparsecheckout true $ echo "Documentation" > .git/info/sparse-checkout # ここで初めてデータを取得しにいく $ time git fetch --depth=1 origin master remote: Enumerating objects: 3960, done. remote: Counting objects: 100% (3960/3960), done. remote: Compressing objects: 100% (3506/3506), done. remote: Total 3960 (delta 356), reused 1918 (delta 287), pack-reused 0 Receiving objects: 100% (3960/3960), 9.30 MiB | 7.35 MiB/s, done. Resolving deltas: 100% (356/356), done. From github.com:git/git * branch master -> FETCH_HEAD * [new branch] master -> origin/master real 0m11.582s user 0m0.259s sys 0m0.126s # `git init`で作られたmasterブランチとfetchしたorigin/masterは全く別物なので、masterを上書きして完成 $ git reset --hard origin/master $ du -sh git_sparse/ git_sparse/.git 16M git_sparse/ 10M git_sparse/.git
git cloneが裏で行っている手順を手動で行っているので手間がかかりますが、こうして出来上がったリポジトリはgit/gitのDocumentationディレクトリだけがcheckoutされた状態なのでとても軽くなりました。
git cloneのオプションまとめ
ここまでの結果をまとめるとこのようになります。
時間(timeのreal)
checkout後のサイズ
-b master --single-branch
--shallow-since="2020/01/01"
sparse-checkout + depth=1
実行時間に関してはあくまで参考値ですが、depthを付けると相当に改善が見込めそうです。checkout後のリポジトリのサイズは、当然ですがsparse-checkoutによってファイルを限定できれば無駄な容量を削減できることが分かるかと思います。
ここまではgit cloneによる1つのリポジトリのケースを見てきましたが、実際の業務ではsubmoduleをたくさん抱えているリポジトリに出会うこともあるかと思います。git cloneにはcloneと同時にsubmoduleを取得し、さらにそのときの振る舞いを制御するオプションも存在します。
歴史あるOSSをいくつか調べてみたのですがサンプルになるようなsubmoduleをたくさん抱えているリポジトリが見つからなかったため、社内のリポジトリを使って実験しました。そのため、詳細なコマンドは省いて結果だけを示します。
時間(timeのreal)
checkout後のサイズ
なし(--recurse-submodules)
--depth=1 --recurse-submodules --shallow-submodules
--depth=1 --recurse-submodules --shallow-submodules -j4
--recurse-submodulesはgit submodule update --init --recursiveと同じ処理をgit cloneと同時にするオプションです。git cloneだけでsubmoudleのセットアップも完了するので便利です。
--shallow-submodulesを付けるとsubmoduleのリポジトリがdepth=1でgit cloneされます。submoduleのリポジトリについて過去のコミットが不要であれば速度の向上、少なくともリポジトリのサイズを減らすことができます。
-j4(--jobs=4)はドキュメントによるとsubmoduleのfetchを同時に行う数とのことです。個人的には4並列にすることでもう少し時間が短縮されることを期待していたのですが、ほぼ変化はありませんでした。
今回実験したリポジトリはsubmoduleの数自体は多いのですが、一部のsubmoduleだけが突出してサイズが大きいという構成上、並列化の恩恵を受けにくかったのかもしれません。あるいは社内ネットワークに由来する何かがボトルネックだった可能性もありますが、今回の本題から外れてしまうので深く調査は行いませんでした。
各CIサービスの対応状況
git cloneの各オプションの効果について理解できたと思いますので、実際のCIサービスでこれらが活用できるのかを見ていきましょう。
なお、調査したのは執筆時点の2020/12です。各サービスとも日進月歩で更新されていますし、フォーラム等で機能要望を受け付けているので将来的には改善されているかもしれません。
CircleCIでは何も意識せずに最初のステップでcheckoutしていると思いますが、実はこの裏ではdepthオプション無しでgit cloneが実行されているため過去の全コミットが取得されています。これはCircleCIのログでCheckout codeのステップのログから確認できます。
CircleCIのサポートからは以下のページにて、checkoutの代わりに自前で--depthや--shallow-sinceオプション付きでgit cloneするか、そのような機能を提供しているOrbsの利用が提案されています。
Shallow Repository Cloning
他に探したところ、30GBを超える超巨大なリポジトリをdepthとsparse checkoutで対応した事例もあるようです。
巨大なリポジトリのJenkinsからCircleCIへの移行においてshallow cloneとsparse checkoutで前処理を高速化する
Bitriseのsteps-git-cloneはデフォルトではdepthのオプションが有効になりません。ですが、clone_depthというオプションを設定するとdepthオプション付きでcloneされるようになります。
https://github.com/bitrise-steplib/steps-git-clone/blob/b1cbe45b5704863c4c930111edeb7aa67d38f5c1/step.yml#L101-L107
sparse checkoutや、submoduleへのdepthの対応は自分がコードを読んだ限りでは無いように見えました。それらの対応が必要な場合は自分でgit cloneのコマンドを書く必要があります。
GitHub Actions
GitHub Actionsのactions/checkoutは、v2からデフォルトでdepth=1のオプションが有効になっています。逆にdepth無しでcloneが必要な場合はdepth: 0を指定するようにとREADMEに書いてあります。
submoduleについてはREADMEに詳細は書かれていませんでしたが、fetch-depthを指定するとsubmoduleを取得するときにもdepthが有効になりそうです。(v2.3.4のコードより)
https://github.com/actions/checkout/blob/v2.3.4/src/git-command-manager.ts#L317
sparse checkoutはサポートされていないようでしたので、必要な場合は自分で一連の処理を書く必要があります。
大規模リポジトリでgit cloneするときのニッチなテクニックを紹介しました。
業務のリポジトリは何年も生き続けて肥大化していく一方なので、CIにおける最初のgit cloneにいつの間にか数分かかってしまうということも珍しくありません。そこはCIサービス側がいい感じに対応してくれていると思いきや、意外とそんなことはなかったりします。
git cloneのオプションについて理解しておくことで、CIサービス側が用意している機能にオプションを渡すだけで改善の余地があるのかどうかを判断できるようになります。最悪、用意されている機能には頼らずに自分でgit cloneのコマンドを書くことでCIの実行時間を短縮できる可能性があります。
git cloneのオプションは他にもたくさん存在しており、なかなか奥深いのでman git-cloneを実行するか、Webのドキュメントをぜひ一読してみてください。
DeNAでは今年、以下の3種のアドベントカレンダーを書いてます!それぞれ違った種類なのでぜひ見てみてください。
DeNA Advent Calendar 2020:DeNAエンジニアによるアドベントカレンダー
DeNA 20 新卒 Advent Calendar 2020:DeNA 20新卒エンジニアによるアドベントカレンダー
DeNA 21 新卒 Advent Calendar 2021:DeNA 21内定者エンジニアによるアドベントカレンダー
この記事を読んで「面白かった」「学びがあった」と思っていただけた方、よろしければTwitterやfacebook、はてなブックマークにてコメントをお願いします。
また DeNA 公式 Twitter アカウント @DeNAxTech では、 Blog記事だけでなく色々な勉強会での登壇資料も発信してます。ぜひフォローして下さい! Follow @DeNAxTech
コミットの歴史を修正してファイル自体がなかったように歴史を改変してしまう場合はその限りではないですが、相当に難しい作業です。↩
.git/objectsの中身について興味がある方は10.2 Gitの内側 - Gitオブジェクトが参考になるでしょう。↩
このrefspecについて理解できるとPinterestで1行追加しただけでgit cloneが99%高速化されたという理由が分かります。↩
swet-blog 2020-12-10 10:00
SWET Testing Blog
SWET (Software Engineer in Test)
macOSのCopy-on-Write機能を使ってディスクを節約した話
夜開催のiOS Test Onlineを開催します!登壇者募集!
Isabelle/Isar勉強会を社内で開催しました
時間を加味したモデリング
この1年すすめていた「プロジェクトの健康状態の可視化と予防」と「自動テストの適用範囲の拡大」という施策についての話


document_lang: ja

langs: ja ru ja ja ja zh ja ja ja ja en ja ja ja ja ja ja ja ja ja ja es ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja zh fr zh zh ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja zh ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja ja ja zh ja en en ja en ja ja en ja ja ja ja ja en ja ja ja ja ja ja ja en ja ja zh ja en en ja ja en ja ja ja ja ja ja ja en ja ja ja ja ja ja en ja ja ja ja ja en ja ja ja ja ja ja ja en zh ja ja en ja ja ja en ja es en ja en ja en ja ja zh ja zh ja ja ja ja ja ja ja ja ja ja ja en ja zh ja en ja ja ja ja ja ja en ja ja ja ja ja ja en ja ja ja en ja ja ja en ja en ja en ja en es en en en ja ja ja en ja ja ja ja ja ja ja en en ja ja zh ja ja zh ja ja zh ja ja zh ja ja zh ja ja zh ja ja zh ja ja zh ja ja zh ja ja zh ja ja en zh en ja ja ja ja ja ja

text:
— カカオ99 (@netinago99) October 21, 2019
— ヤマヤマ いいね🚧中です (@mNH2QFwOxnxYBlh) October 21, 2019
一方、今まで預かっていた熱田神宮はガチ晴れ
天叢雲剣の実物は、熱田神宮(あつたじんぐう)の御神体「草薙神剣」(くさなぎのみつるぎ:別名・草薙剣[くさなぎのつるぎ])として祀られています。
今回、前日は大雨の予定でしたが、皇居に運ばれたためか、熱田神社は晴れでした。
即位礼正殿の儀の行われた祝日。
お祝いにムードの熱田神宮にて。#イマソラ pic.twitter.com/FLSNQtPoEB
— 一刻館 (@ikkokukandesu) October 22, 2019
もうすぐ出番です#熱田神宮 #巫女 #迦陵頻 #舞楽 pic.twitter.com/fgbE8XsEW7
— 熱田神宮(公式)/AtsutaJingu (@atsuta_jingu) October 22, 2019
即位礼正殿の儀はじまる。直後、皇居の空が晴れて虹がかかる。
「即位礼正殿の儀」が始まる13:00頃、途端に千代田区上空の雲が割れ、晴れ間が除くとともに、空に虹が掛かりました。虹がかかることも少ない東京で、ちょうど即位礼正殿の儀の日に虹がかかるとはものすごいタイミングですね。
開始と共に雨が上がり虹が発生したようで凄いパワーですね。。
🌈 pic.twitter.com/Qi8eVOh3ER
— 😺🐈 【コウスケ】Official TW「For All Traders」🐈😺 (@KOUSUKE_TRADER) October 22, 2019
即位礼正殿の儀に合わせて雨が上がり東京の空に虹がかかる。壮大過ぎる演出にただただ驚くばかり。#即位礼正殿の儀 #即位の礼 pic.twitter.com/uOZ4fxTfzD
— ダッダ~ンのボヨヨヨヨ~~ン (@masato_yawa) October 22, 2019
即位礼正殿の儀が始まると雨が上がり、我が家からも虹が見えました。
一日本国民として、連綿と続く皇室の安泰をお祈り申し上げます。 pic.twitter.com/gP8xaFXmzs
— 小野寺浩太 (@romaneeconti910) October 22, 2019
晴れのタイミングで神遣いであるカラス(八咫烏)の鳴声がしていた。
僕もライブビューイングで見ていましたが、たしかにカァカァと何度もカラスが鳴いていました。
八咫烏(やたがらす)とは、日本神話に登場するカラス(烏)であり神です。
八咫烏は、日本神話において、神武天皇を大和の橿原まで案内したとされており、導きの神として信仰されています。一般的に三本足の姿で知られ、古くよりその姿絵が伝わっており、また、太陽の化身ともされます。
即位礼正殿の儀、直前まで雨が降ってて(天叢雲剣)儀式中は晴れて太陽が覗いて(天照大神)前後には烏が鳴いてるのが聞こえて(八咫烏)虹まで出てた(なんか色々)の本当に神懸かってたよね…
— とーすと (@tkisttkn) October 22, 2019
追い打ちをかけるように富士山も初冠雪の状態で顔を出した。
ちょうどこの日に、富士山が初冠雪の状態で顔を出したそうです。
今日は「即位礼正殿の儀🎌」お祝いの日に富士山も初冠雪⛄というサプライズで雪化粧の姿を見せてくれました(*☻-☻*)
夕暮れ時、雲も有りましたが☁少しだけ染まった富士山もまた、美しい姿でした〜👸💐 pic.twitter.com/Z7LvITl3Jn
— ヤマヤマ いいね🚧中です (@mNH2QFwOxnxYBlh) October 22, 2019
虹もそうだけどずっと雨だった静岡で即位礼正殿の儀の後、晴れて富士山が顔を出すってもう鳥肌もの… pic.twitter.com/vBwTFQWnrb
— nyago (@nyagonesi1007) October 22, 2019
日本に近づいていた台風20号は消滅し21号は進路を変えた。
ちょうどこの前に、2つの台風が日本に接近し、上陸間近と言われていました。台風20号はもう二三日前に温帯的圧になっていましたが、21号も進路を変えました。
即位礼正殿の儀により台風20号は消滅し台風21号は進路を逸れ日本は守られた 天皇強い…
pic.twitter.com/Wpi2h3F6YE
— 恋㌨ (@Ls23867665) October 22, 2019
即位礼正殿の儀のおかげかなんか知らんけど台風20号は進路変えて、21号は消滅したって流石やなぁ
この写真は@Haruka_tennis31 様提供 pic.twitter.com/iZKCA7ABof
— えいちきゅう (@hq_v_) October 22, 2019
ちなみにNHKを見ていたら解説の人が「昭和天皇は晴れ男エンペラーウェザーと呼ばれた」と言っていました。エンペラーウェザーとかスタンドとして強そうすぎますね。
静岡浅間神社で桜が咲きました。浅間神社といえば、主祭神は木花咲耶姫命という桜の神様です。
静岡浅間神社で桜が咲いたらしいですよ!
この神社の主祭神である木花咲耶姫命は初代天皇の曾祖母にあたるらしく…
とても珍しいことのよう。
本当に色々神がかってる事が起こってますね😅すごい。#即位の礼 #即位礼正殿の儀 pic.twitter.com/TIdl5CB2Eu
— 大野 潤 🐈 7ヶ月(6monthes🤰) (@kickoffjun) October 22, 2019
などなど、以上のようなことが置きました。※事実です。
本当に盛りだくさんな一日となりました。
伝説にしても盛りすぎ!な意見
ジジィになったときに「令和の即位礼正殿の儀はどうでしたか?」って聞かれて
「朝から土砂降りの雨がふっておったが、即位礼正殿の儀のタイミングで雨が止み、晴れてきて空には虹がかかり、富士山も初冠雪の状態で顔を出した」なんて言ったら「このほら吹きジジィ!」とかいわれるけど事実だもんな。
— polaris (@Polaris_sky) October 22, 2019
歴史とか調べてる時に「天皇が即位した年は天災が酷く、即位した際には天が喜び雨が一時止み、空には虹がかかった。」みたいなこと書いてあったら普通「ははーん、面白いけど後世の歴史家とか時の権力者が箔をつけるために盛った捏造だろうな」とか思っちゃうもんな…
— ぶし@9月12日単行本1巻発売! (@bushi218) October 22, 2019
これで孫ができたら、「天皇陛下が即位するまで、休日は悪天候が続き、台風まで来たんじゃ。
しかし即位当日の日、即位の瞬間に晴れたんじゃよ。」
って昔の伝説を語る爺さんになる事ができる。
— 鰐軍曹 (@WANIGUNNSOU) October 22, 2019
今日起きたことが全て偶然だとしても、
・天叢雲剣(草薙剣)の神威と思わせられる雨と雲
・儀式完了と共に見えた晴れ間と虹
・富士山が見えただけでなく初冠雪
・木花之佐久夜毘売を祀る神社で桜が咲く
・台風が直撃ルートから東へ逸れる
ここまで揃うとヤバみ凄 火山列島の言祝ぎが聞こえてくるよう
— 🎾🌸磯宮わかめ🌸🍽 (@pl0w0lq) October 22, 2019
鐘の音が鳴り、侍従・女官によって高御座・御帳台の帳が開けられ、天皇陛下、皇后陛下がお姿を見せられました。#即位礼正殿の儀 #天皇皇后両陛下 #tbs pic.twitter.com/58Noh8PfGo
— TBS NEWS (@tbs_news) October 22, 2019
午後1時すぎから行われた「即位礼正殿の儀」。
秋篠宮さま、紀子さま、眞子さま、佳子さまが正殿・松の間に入室された際のご様子です。#即位礼正殿の儀 #皇居 #tbs pic.twitter.com/XVmvJDznpX
— TBS NEWS (@tbs_news) October 22, 2019
即位礼正殿の儀で安倍総理が寿詞、万歳三唱の掛け声に合わせ陸上自衛隊が礼砲
pic.twitter.com/L3X8hYjCYZ
— 南洲 (@yuewanwan) October 22, 2019
即位礼正殿の儀で来日中のムスワティ3世、国民に5年間の性行為禁止を命じておいて、真っ先に自分が破った挙げ句罰金牛1頭支払ってるって記事滅茶苦茶すぎる。 pic.twitter.com/umcZOQgqjs
— ぜんらまる (@zenramaru) October 22, 2019
「即位礼正殿の儀」終わるpic.twitter.com/7LNqKhl3ZK
— Alfons López Tena #FBPE (@alfonslopeztena) October 22, 2019
いよいよ #即位礼正殿の儀
NHK「即位礼正殿の儀」
日テレ「即位礼正殿の儀」
TBS「即位礼正殿の儀」
フジ「即位礼正殿の儀」
テレ朝「即位礼正殿の儀」
テレビ東京 「即位礼正殿の儀」
な.........んだと!?!? pic.twitter.com/uCA5no10Ax
— はなたろう (@hana_taro2014) October 22, 2019
即位礼正殿のゲストが話題に
ブルネイ国のマティーン国王がイケメンだと話題に
即位礼正殿の儀のゲストで来てる、白軍服のイケメンはブルネイ🇧🇳のマティーン国王だそうです。
なおペットはトラだそう。
王子様速報、こちらからは以上です。#三種の神器10連ガチャ #即位礼正殿の儀 pic.twitter.com/Dvd6Y2048b
— Lightslj (@d123951) October 22, 2019
オランダのマキシマ王妃いつもの太陽の笑顔。淡い色のドレスも素敵。#即位礼正殿の儀 pic.twitter.com/1WKToTsbNx
— しまこ (@SHIMAKO000) October 22, 2019
即位礼正殿の儀と饗宴の儀のロイヤルの皆様。
レティシア王妃(スペイン)
マキシマ王妃(オランダ)
メアリー王妃(デンマーク)
マティルド王妃(ベルギー)
ヴィクトリア王太子(スウェーデン) pic.twitter.com/Ba6AqKbZre
— aaa (@aicolog) October 22, 2019
共演の儀へ向かう各国の国賓の衣装が話題でした。
備忘録追加。#饗宴の儀
1枚目、オランダ国王ご夫妻。マキシマ王妃、珍しい感じのヘアとメイク。貫禄。
2枚目、ベルギー国王ご夫妻。マチルド王妃、落ち着いたゴールドのドレス素敵。
3枚目、スペイン国王ご夫妻。レティシア王妃、珍しいお色のドレス。手の込んだ刺繍(?)ですね。#即位礼正殿の儀 pic.twitter.com/q2Li9hmANN
— しまこ (@SHIMAKO000) October 22, 2019
備忘録さらに追加。#饗宴の儀
1、2枚目、スウェーデンのカールグスタフ国王と娘のヴィクトリア皇太子殿下。スウェーデンは次は女王様。その次も女王様。(かわいいプリンセスがいらっしゃる)
3枚目、デンマークの皇太子ご夫妻。メアリー皇太子妃のファッション大好き。#即位礼正殿の儀 pic.twitter.com/9CVv3WhYlb
— しまこ (@SHIMAKO000) October 22, 2019
備忘録また追加。#饗宴の儀
ブータン国王ご夫妻。ブータンの伝統的な装束でしょうか。王妃の差し色のブルーが素敵。
伝統的な装束の方は他にもたくさんいました。皆様、とっても素敵でした。#即位礼正殿の儀 pic.twitter.com/JLJTmmoRK3
— しまこ (@SHIMAKO000) October 22, 2019
どこのお国の方々かは分からないけどドレスが素敵で見てて笑顔になる✨#饗宴の儀 pic.twitter.com/X9Esx88C2m
— 咲海 (@saku_mi9999) October 22, 2019
各国要人のコレクション。女性ファッションのまとめメモ。夜の部。#饗宴の儀 #即位礼正殿の義 pic.twitter.com/W1PpkYQHT2
— test (@manzaHS) October 22, 2019
うえー!素敵すぎる~!!#饗宴の儀 pic.twitter.com/vaH65lAIuQ
— 咲海 (@saku_mi9999) October 22, 2019
感想をどうぞ コメントをキャンセル
メールアドレスが公開されることはありません。 * が付いている欄は必須項目です
次回のコメントで使用するためブラウザーに自分の名前、メールアドレス、サイトを保存する。
email confirm*
日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)
カテゴリのおすすめ記事
【漫画】読み始めると止まらない!ネットでバズった面白いWEB漫画 まとめ
ここではネットでバズった面白い漫画を集めました。ストーリーが面白い話、ためになる話、笑ってしまう話、考え點せられる話など、いろいろな話を集めています。暇つぶしにご覧ください。気に入ったものがあれば作者さんをフォローしてみてください。 書籍化などがされている漫画は、終わりに書籍ページへのリンクなどが載 ...
【ファン必見】Fate・FGOの話題・感想・評価のまとめ
ここではFate関連の話題をまとめました。大喜利や、イベントの感想、キャラの話やお笑いなど雑多にまとめています。 笑える話もありますので、ぜひ見ていってください。 目次FGO・fateの話題まとめたらればさんのFGO感想・攻略記FGOの皆の反応30秒でキャラクターを紹介するイラスト・漫画などFGOの ...
【話題】ネットで無事に萌えキャラにされた「八尺様」が話題【ポポポ】
スポンサーリンク 初めは2ch(5ch)発祥の怖い話でしたが、無事にネット民によって萌えキャラにされた八尺様が話題です。 目次ネットで萌えキャラにされた「八尺様」八尺様「ポーポポ」八尺様とジャック・ハンマー八尺様を捕まえたショタ夏を満喫する八尺様八尺様・・・?八尺様のおねショタ八尺様大人気ない八尺様 ...
【話題】8つの質問に答えて丁寧な感想を作れる「感想自動作成ジェネレーター」が話題に
スポンサーリンク 好きな作者さんに感想を伝えたいけれど、どう書けばいいかわからない・・。そんなときに8つの質問に答えるだけで丁寧な文章を作成できる「感想自動作成ジェネレーター」が話題です。 目次「感想自動作成ジェネレーター」とは?「感想自動作成ジェネレーター」の使い方まとめ 「感想自動作成ジェネレー ...
【漫画】アドバイス罪というのが存在するらしい
スポンサーリンク 目次アドバイス罪皆の反応 アドバイス罪 Twitterで「アドバイス罪」という話が話題になっていました。 アドバイス罪というのが存在するらしい。頼んでないのに説教始める人っているねぇ。気持ちいいのかな。 — たもさん@カルやめ発売中 (@tamosan17) Novem ...
【怖い話広告チャレンジ】怖い話読んでたのに広告に全部持ってかれた話が話題
スポンサーリンク 怖い話読んでたのに広告に全部持ってかれた話が話題です。 怖い話広告チャレンジ 一番初めの元ネタツイートがこちらです。 @etokanoyatu 怖い話読んでたのに広告に全部持ってかれた — 柚野🏒🗿🛰 (@etokanoyatu) April 18, 2020 @na_ ...
【現場猫】最新版ファイルがどこかわからない・・現場あるある
スポンサーリンク 目次最新版のファイルどこ…?ここ?皆の反応データ整理シリーズ皆の反応 最新版のファイルどこ…?ここ? 最新版のファイルどこ…?ここ? #現場猫 pic.twitter.com/WcGnyVM2TK — からあげのるつぼ (@karaage_rutsubo) May 11 ...
【殿堂入り】ニコニコで100万回以上再生された伝説のボカロ動画をご紹介します【ボカロ曲】
ここではニコニコで有名になったボーカロイド曲を、殿堂入りから最新曲までご紹介しています。 過去の最盛期を彩った伝説の曲から最新のものまでボカロ曲をご紹介。 また、それぞれの曲に「歌ってみた」の人がいます。曲が気に入ればぜひお気に入りの歌手を見つけてみてください。 スポンサーリンク 目次殿堂入りのボカ ...
【ホラー映画】アリ・アスター監督の地獄を無料で味わえると話題に
スポンサーリンク 『ヘレディタリー/継承』『ミッドサマー』でおなじみ、アリ・アスター監督の短編ホラー作品を、YouTubeで無料で楽しめると話題です。 目次アリ・アスター監督の短編がYouTubeで見れる短編映画の動画リンク“最悪なトイストーリー3“でおなじみ『Munchausen』(ミュンヒハウゼ ...
2021年版!Amazonブラックフライデーセールで買いたいものまとめ
人気商品がお得に買える「2021年 Amazonブラックフライデーセール」が始まりました! ここではブラックフライデーで人気の商品や皆が買っている売れ筋商品をまとめました。 またビッグサプライズセールやタイムセールでさらに安くなる可能性もあるので、気になるものは「ほしい物リスト」に入れてタイムセール ...


langs: ja id ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja de ja zh ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja zh ja ja ja ja ja ja zh ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja zh ja ja pt ja ja ja ja ja ja ja ja ja ja ja ja ja ja zh ja ja ja ja en ja en ja ja ja

text:
全くと言っていいほど家族の情報などが表に出ないIU。そんなIUの弟がYOUTUBEに出演し話題となりました。
この動画から見てわかるように非常に仲の良い姉弟だと言うことが見て取れます。
若くしてデビューし忙しい日々を過ごしていた姉と、17歳からアメリカへ留学していたと言う弟。
共に過ごした幼少期の時間は短いかもしれませんが、今このようにして楽しそうにしている二人を見ると感慨深い気持ちになりますね、、、(誰目線)
IUと弟の生い立ちが壮大だった
幼少期のIUと弟の家庭環境は経済的に安定していたそうです。
ところが両親が知人の連帯保証人になったとこから自体が急変し、借金を背負うことに。
借金の取り立てに苦しめられた結果、IUと弟は祖母と共に親戚の家の一部屋で3人で暮らすことになったそうです。
別の情報として父親の借金や、母親の借金など様々な情報が飛び交っているためどれが正しいかは定かではありませんが、家族が多額の借金を背負ってしまったことには変わりはなさそうです。
その時も生活は苦しく、歌手としてデビューすることを目指していたIUは親戚から罵倒を受けることもあったそうです。
中学一年生の時に罰として歌ったことがきっかけで歌手を目指したIU。当時小学生であったIUの弟が経験した姉へ対する親戚からの罵倒は精神的にも追い詰められたに違いありません。
IUと弟の仲良しエピソード
壮大な生い立ちから見事歌手デビューしたIUと、わずか17歳で留学した弟。そんな2人ですが非常に仲が良いと言われています。
先ほどお伝えしたようにIUのYOUTUBEチャンネルに弟が出演しています。
その際の様子で仲が良いことが十分見て取れるでしょう。動画では質疑応答をしているIUと弟。質問の間にも会話をし、笑いの多い映像となっています。動画内のIUと弟の笑顔を見ると非常に仲の良いことがわかります。
またIUと弟のカカオトークの内容もIUが公開しています。
IU(アイユー) と弟が交わした過去のカカオトークが話題。2人のやり取りが可愛すぎ。。
・黄色の吹き出しがIU、白色の吹き出しが弟
・IUは「ヤ(ねぇ)」を連投…
つづき⇒ https://t.co/wrhQLi5B8D
速報ランキング⇒ https://t.co/mc9qWCoA44 pic.twitter.com/SP1rTSNd6c
— 韓流ツイッター (@kor_celebrities) November 7, 2019
私にも少し歳の離れた弟がいますが、一般人の兄弟と同じような会話をIUと弟もしているようです(笑)
国民的妹のIUも弟とこのような会話をすることがわかって親近感が湧きますね。
ここまでIUと弟について様々なエピソードを紹介してきました。
いかがでしたでしょうか。
国民的妹のIUと弟が送ってきた壮大な過去から、今を楽しく生きるIUと弟の姿。
その関係性はどこにでもいる普通の兄弟とさほど変わりはないでしょう。
そのためこの記事を読んでIUと弟に親近感を湧いた方もいるのではないでしょうか。
まだまだIUの家族のエピソードは少ないですが、今後彼らの幸せなニュースがたくさん飛び込んでくることを祈っています!!!そしてさらなるIUの飛躍を祈って終わりとさせていただきます。
最後まで読んでくださりありがとうございます!
よかったらシェアしてね!
URLをコピーしました!
URLをコピーしました!
双子がいた!ブチギレ氏原の双子の姉の画像や職業・2人の関係についてまとめた
【学歴まとめ】ブチギレ氏原の出身高校や大学を調査してみた
出身は鹿児島の指宿市?大平くるみの地元が出身中学から判明!?
【実家情報】基俊介は東川口出身?家はどの辺にあるのか調査してみた
【画像】ぼっちぼろまるの顔は?性別は男で正体は一体誰なのか
埼玉栄高校時代を調査!新川優愛はスポーツ万能成績優秀な才女だった! に 館山飛龍 より
天月と96猫は付き合ってる?2人の結婚の噂や関係を深掘りしてみた! に 天月の結婚相手が96猫になる可能性は0%!その根拠と理由を解説します! - 芸能の森 より
【学歴】Eveの出身高校・大学は?学生時代はスポーツ少年だった! に Eveの素顔はイケメンで確定!顔をあかさない理由は作品に必要がないからだった! - 有名人調査隊 より
【ググミン】ジョングクとジミンの熱愛は事務所公認!キスやおそろの証拠画像がこちら に BTSジョングクの性格がやばい!メンバーからの評価もすごい! - 有名人調査隊 より
【画像】ジョングクの兄弟写真が見たい!兄とのツーショットや仲良しエピも! に ジョングクの兄・チョンジョンヒョンとの兄弟仲がよすぎてやばい!譲渡した高級マンションの金額がすごすぎる! - 有名人調査隊 より


langs: ja ja en ja ja ru ru ja ja ja ja ja ja ja hbs_lat en ja ja ja uk ja ja ky

text:
お客さまが現在お持ちのドメインを自社のサーバーに移管した後、私達が管理いたします。
Q. 他県にいてもホームページの制作依頼をすることは可能ですか?
はい可能です。制作前にお客さまのお店(会社)に一度伺い、その場でサービスの詳細と、実際にどんなホームページをつくるのか?どんな特色を出していきたいのか?など実際にホームページ制作に必要となるコンテンツのヒアリングを行います。そしてその内容を自社に持ち帰り、ヒアリングの内容にそって制作をおこないます。その他の打ち合わせは電話での打ち合わせとなります。


langs: ja ja ja ja ja ja ja ja de ja ja ja ja ja en de ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja war ja ja ja ja ja ja en de ja ja ja ja ja ja ja ja ja ja ja zh ja ja ja ja ja ja ja en de ja ja ja ja ja ja ja fr ja ja ja ja ja ja ja ja ja ja en de nl en ja ja ja ja ja en ja ja ja ja ja ja ja en de ja ja ja ja ja ja ja de ja ja ja ja ja ja ja ja ja hbs_lat ja ja hbs_lat tl ja hbs_lat ja ja hbs_lat it ja hbs_lat en ja hbs_lat ja ja hbs_lat ja ja hbs_lat ja ja hbs_lat ja ja hbs_lat ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en en de ja pl nl en it ja en war pt hbs_lat pt ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja zh ja ja ja ja ja zh zh zh zh ja ja ru zh zh ja ja ja ja zh ru pt zh ru zh zh zh ja ja de ja de ja de ja de ja de ja de ja de ja de ja de ja de es en ja zh ja ja ja ja ja ja ja ja ja ja ja en en de ja pl nl en it ja en war pt hbs_lat pt ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja zh ja ja ja ja ja zh zh zh zh ja ja ru zh zh ja ja ja ja zh ru pt zh ru zh zh zh ja ja de ja de ja de ja de ja de ja de ja de ja de ja de ja de es en ja ja

text:
こちらが都度返信をしないと配送がどんどん遅れてしまいますのでご注意ください。
私が購入した店舗では、設置日の前日に設置業者から
明日の○○時にお伺いしますがご都合いかがですか?
という確認電話がありました。
それに合わせて今ある洗濯機を最後に回すのはいつなのか、いつ水抜きをしたらいいのか、などの作戦を立てます。
我が家は古い洗濯機は自分で処分する予定だったため、洗濯機を家の端っこに移動させ、水が垂れないように大きな袋で囲ったりと朝から大忙し(家電リサイクルで引取りを申し込んでいる人は発生しない作業です)。
時間になると設置業社さんが予定通り来てくれて、そんな広い家ではないのにスムーズに搬入してくれます。
特に洗濯機置き場周りは申し訳ないほど狭いのですが…
無事に設置していただけました。簡単に試運転を行い、引き渡し完了です。
トラブルなく設置完了まで済み、なおかつお得に購入することが出来たドラム式洗濯機。
私はネットでこれほどの大型高級家電を買ったことがなかったのですが、次の5つのことに注意することで満足な買い物ができました。
チェック1.設置見積もりを依頼しましょう
まず1つ目に気を付けることが「設置見積もり」です。
ドラム式洗濯機を初めて購入する人や、サイズアップをする人は必ずやったほうがいいです。
設置見積もりは大型家電を購入するときに「搬入経路」や「設置場所」に問題がないかを調べてくれるサービスです。
これはヨドバシカメラ・ビックカメラ・ヤマダ電機などの大手家電量販店に依頼すると無料で行ってくれます(洗濯機の場合は無料、エアコンは有料のところが多いようです)。
私も当初は実店舗で購入する予定だったのでヤマダ電機に依頼をして見積もりをしてもらい、無事設置OKの判定が出ました。
設置見積もり時には購入する型番は確定していませんでしたが、見積もり業者さんに購入候補を伝えて設置の可否を判断して貰えましたよ。
ネット購入時もストアによっては設置見積もりを行ってくれるところがありますが、あくまでも注文後のサービスな場合がほとんど。
スムーズに注文〜設置まで済ませたい場合には事前の見積もりを強くオススメします。
実店舗で設置見積もりをしたあとにインターネットで購入するのはなんだか悪い気がして…
という方は、ぜひ設置見積もりをして頂いた店舗のネット通販を利用してあげてください。
私はお得にドラム式洗濯機を購入するのに楽天市場・ヤフーショッピングがオススメなのですが、
ビックカメラは楽天市場店
ヤマダ電機は楽天市場店とヤフーショッピング店
ケーズデンキは楽天市場店とヤフーショッピング店
など、各モールに出店がありますので是非そこからお買い上げください。
チェック2.搬入・設置の条件を確認しましょう
次に気を付けたいのが搬入・設置の有料条件です。
実店舗だと店員さんから事前に説明がありますが、ネット購入の場合は自分で商品説明をしっかりと読む必要があります。
送料の有無はECサイトの仕様で目立つところに書かれていますが、設置料や搬入費は商品説明に記載されていることが多いです。
価格に設置料が含まれているのか➡商品名か商品説明に「設置料込み」などと書かれていることを確認
搬入で有料になるケースはあるのか➡商品説明を確認。記載がない場合は質問するのがオススメ。
特に搬入については2階以上でエレベーターなしの集合住宅の場合は発生する可能性があります。
戸建てやエレベーターがあるマンションの場合には多くの店舗で無料のはずですが、これらの販売条件を事前にチェックしておきましょう。
チェック3.引取り条件を確認しましょう
今使っている洗濯機を処分する予定の人は家電リサイクル費についても確認しておきましょう。
ストアによって対応が異なりますが、多くの場合で次のいずれかに当てはまります。
価格に回収&リサイクル料が含まれる(タイトルにリサイクル込みと書かれている場合が多い)
別途料金がかかり、「リサイクル費」という商品をカゴに入れて同時に支払いをする必要がある
別途料金がかかり、購入後に案内のメールが届く(振り込み等で支払い)
回収&リサイクルは行っていない(自分で処分を手配する)
どれも商品説明に必ず記載があるはずなので、事前にしっかりとチェックをしましょう。
なお個人的には今使っている洗濯機が故障していないのであれば、フリマアプリなどで売るのがオススメです。
詳しくは後述しますね。
チェック4.保証条件を確認しましょう
「搬入・設置」「引取り条件」などを確認したら、最後に「保証条件」を確認しましょう。
こちらもストアによって対応が異なりますが、多くの場合で次の2つに当てはまります。
メーカー保証のみ(1年保証)
5年保証付きで価格に保証料が上乗せされている(商品名に書かれていることが多い)
なお購入商品がNA-LX129A、NA-LX127A 、NA-VG2600のいずれかの場合にはPanasonic公式の保証サービス「Panasonic Care」に加入できます。
なのでネット購入時にはあえてメーカー保証のみで購入し、購入後にPanasonic Careに加入するのもアリだと思います。
チェック5.お得な日を確認しましょう
最後に確認してほしいのがお得な購入日を狙うことです。
実店舗であれば購入日によるお得度はそれほど変わりませんが、ネット購入の場合には万単位で変わってくることがあります。
例えば楽天市場の場合には
お買い物マラソンなどのイベント
などをチェックします。
特にSPUは大事で、1〜2ヶ月前くらいから準備が必要なものもあるのでじっくりと、時間をかけて整えましょう。
35万円の買い物の場合、SPUが3%違うと1万円を取り逃がすことになります。
またヤフーショッピングの場合にも
PayPay STEP
5のつく日や買う買うサタデーなどのイベント
PayPayカードの準備
など、事前に準備することが盛り沢山です。
ヤフーショッピングに関しては当サイトで先出しのイベント情報をまとめていますので、興味のある方は参考にしていただければと思います。
【2022年9月後半】お得な日はいつ?いつ買う? ヤフーショッピングとPayPayモールの買い時とキャンペーンまとめ。
ヤフーが運営するヤフーショッピングとPayPayモール。最近では最大手の楽天を意識して、ポイント還元にかなり注力している印象です。そんなヤフーショッピングですが「お買い物をする日」によってポイント還元率が大きく異なります。これはヤフーショッピングだけに限らず、楽天・auPayマーケット・dショッピングなどすべてのECサイトに共通することですが、ヤフーショッピングは特にその差が大きいです。...
money-hensachi.com
古い洗濯機はメルカリで売るのがオススメ
最後に蛇足ではありますが古い洗濯機の処分について触れておきます。
次の2つに当てはまる人は、今使っている洗濯機をフリマアプリで売却することを強くオススメします。
まだ普通に動く・使える
ドラム式洗濯機の購入時に有料で家電リサイクルを申し込む予定である
例えば今お使いの洗濯機が5,000円で売れた場合、儲かるのは売り上げの5,000円だけではありません。家電リサイクル費用の約5,000円も浮くため、合わせて1万円を得することになります。
洗濯機の梱包・発送なんて面倒で出来ないよ!
と思う方もいるはずです。私もそう思います。
ですがメルカリでは梱包から発送まで、全てお任せできる「梱包・発送たのメル便」というサービスがあるのをご存知でしょうか?
これはヤマト運輸と提携しているサービスで、
しかも通常料金よりお安いフリマ特別価格
私は今回これで洗濯機を売却しましたし、過去にテレビも売ったことがあります。梱包のストレスが一切ないのは本当に助かります。
【家具・家電】PayPayフリマで大型商品が送れる「おまかせ大型配送」が開始。安いの?メルカリと比較。
2020年11月下旬にPayPayフリマに新しい発送方法が追加されました。その名も「おまかせ大型配送」です。名前の通り「大型の商品」を「おまかせ(集荷も梱包もプロがしてくれる)」で発送してくれるサービスです。本ページではおまかせ大型配送の概要と、高いの?安いの?オススメなの?メルカリのたのメル便との違いは?などを解説します。おまかせ大型配送とは「おまかせ大型配送」...
money-hensachi.com
ちなみに私は 国内メーカーの2014年製洗濯機 をメルカリで売ったのですが、11,000円程度で売れたため送料5,000円(200サイズ)がかかっても5,000円ほど手元に残りました。
元々お金をかけて処分するつもりだったのでかなり得した気分です。
この大型商品をおまかせ発送できるのは本記事作成時点でメルカリとPayPayフリマのみです。
PayPayフリマは販売手数料は安めですが、メルカリのほうが圧倒的に利用者が多い = 売れやすいフリマアプリです。
洗濯機は売れるまでは家のどこかに保管しておく必要があるので、正直邪魔です。
私は数%の販売手数料よりも早く売れる方を優先したためメルカリを選びました。
これから「メルカリを利用しようかな」という人は下記の紹介コード
を会員登録時に入力いただければ500円相当のポイントが貰えます。
良かったら使ってやってください。
高い買い物でしたが事前に準備をしっかりしていたため、満足度の高い買い物ができました。
製品自体も文句のつけようがなく、毎日本当に助かっています。
洗濯干しは不要ですし、洗濯開始から2〜3時間でふわふわな衣類・タオルが使える生活はもう抜け出せません。
当初は高い買い物だから実店舗で…という固定概念がありましたが、ネットで購入しても結局届くものは同じ。
お得度としては数万円違ったので、初めてのドラム式洗濯機でしたがネットで購入して良かったと思えました。
【値引き不可?】Panasonicのドラム式洗濯機 2022年最新モデル 安くお得に買う方法。NA-LX129BL/127BL/125BL/113BL
2022年10月にパナソニックからドラム式洗濯機の最新モデルが発売されます。2021年モデルは11月に発売されたので、わずか11カ月での新モデル発表となりました。今や「ドラム式洗濯機」は「ロボット掃除機」と「食洗器」とならぶ、「新・三種の神器」の1つに数えられていて、時間がない現代人に欠かせない家電の1つになりつつあります。その中でも特にPanasonicのドラム式洗濯機...
money-hensachi.com
読めばいつか得するかも…?な当ブログは【毎日更新中!】
Twitter または Instagram で更新情報をアップしてるので、是非フォローお願いします!
2022年7月21日に惜しまれながら終了した、現金チャージでAmazonギフト最大2.5%還元が2022年9月27日までの期間限定で復活しています!
ただし対象者限定のようで、対象外の人にはエントリーボタンが表示されないとか…
https://amzn.to/3q2zyIv
とりあえずエントリーページで対象かどうかの確認をオススメします。
前イベントと同様であれば、Edy・Suica・nanacoチャージでも還元されるので激アツです!
そのキャンペーンってなに?なんで現金チャージなのにEdyやSuicaが関係してるの?という人は、こちらで詳しくまとめているのでどうぞ!
【終了】Amazonギフト券の現金チャージで最大2.5%還元が終わりへ… 代わりのキャンペーンやオススメのチャージ方法は?
現金チャージで最大2.5%還元が2022年9月27日までの期間限定で復活しています!ただし対象者限定のようで、対象外の人にはエントリーボタンが表示されないとか…とりあえずエントリーページで対象かどうかの確認をオススメします。エントリーするタロウ前イベントと同様であれば、Edy・Suica・nanacoチャージでも還元されるので激アツです!「お得なA...
money-hensachi.com
本ブログ管理人。札幌在住です。
できるだけ毎日更新しています。
目指すべきサイトは「ブックマーク・お気に入りに入れておくだけで、ちょっと得するサイト」です。
Twitter・インスタで更新情報をつぶやきますのでフォローお願いします!
マネー偏差値をフォローする
ドラム式洗濯乾燥機買い物
NA-LX113ALNA-LX125ALNA-LX127ALNA-LX129AL
TwitterFacebookはてブPocketLINEコピー
マネー偏差値をフォローする
日常的マネー偏差値向上ブログ
【実践】無印良品の店舗受け取り。試着・返品・キャンセル・支払い方法は?良品週間は?
2021年8月権利分 イオン(8267)の優待返金引換証が到着。いつ届く?優待利回りは?換金手順とキャッシュバック対象外は?
PayPayモールについにZOZOTOWNが登場!最大30%還元!条件・上限は?いつから?いつまで?
【2022年9月後半】お得な日はいつ?いつ買う? 楽天市場の買い時とキャンペーンまとめ。次回お買い物マラソンはいつ?
2021年9月期 ジョーシン(上新電機)の株主優待が到着。1株保有で利回り230%超!!楽天・ヤフーでは使えない?
【Amazonランキング1位】Anker Eufy RoboVac 11S おすすめロボット掃除機 レビュー・口コミ
ドラム式洗濯機で粉洗剤・粉石けん・粉末漂白剤は使える? 直接入れる?溶け残り・オススメは?パナソニックNA-LX129AL/R
[楽天Pay編]1つのアカウントを複数端末で使えるのかを検証
セイコーマート(セコマ)のペコママネーはお得なのか?有効期限は?現金派には是非使って欲しいカードです。
大塚商会「たのめーる」と「ぱーそなるたのめーる」の違いは?オフィス用品が個人でも買える?安いの?送料は?
【経費に出来ない?】ひとり社長の健康診断費用を安くお得にするオススメの方法。個人事業主・同族経営企業も。
【聴き放題】Amazon Audibleがコイン制を終了・廃止へ。退会後の扱いに要注意。
コメントをどうぞ コメントをキャンセル
メールアドレスが公開されることはありません。 * が付いている欄は必須項目です
次回のコメントで使用するためブラウザーに自分の名前、メールアドレス、サイトを保存する。
LINEMO(ラインモ)4
ドラム式洗濯乾燥機11
日本郵便・ゆうちょ40
【1番高いのは?】マクドナルドの株主優待でお得な最高値商品を頼みたい!どれが頼める?2022年9月
【2022年9月】すきパス(Sukipass)で対象外の商品は? ランチセット・スキミックス・うな丼・うな牛・期間限定にも使える?
【反応しない】Fire TV Stickリモコンで電源・音量ボタンが効かない・動作しない場合の設定方法。
2021.04.122021.08.03
【比較】バーガーキングのワッパーとワッパージュニアとは?マクドナルドのハンバーガーと比べてみた 大きさ・価格の違い
2022.05.092022.09.06
【どこで買える?】PayPayギフトカードとは。コンビニで購入できる?購入場所や購入方法は?
【2022年9月】auスマートパスプレミアム(スマプレ)の特典・クーポンまとめ 今は入るとお得?Relux・ケンタッキー・マクドナルド
2022.09.032022.09.07
【2022年~2023年版】宝島社 ぴあ 飲食店ファンブックまとめ。おすすめ3選! 今買うならこれ!元を取りやすいのは?
2022.07.102022.07.11
【2022年9月後半】お得な日はいつ?いつ買う? ヤフーショッピングとPayPayモールの買い時とキャンペーンまとめ。
2022.09.162022.09.24
【履歴消去】Amazonプライムビデオの履歴を消したい。テレビ・スマホ・アプリから削除する方法まとめ。
2021.04.062022.06.25
すき家 タッチパネル式の持ち帰り注文利用手順。支払い方法は?株主優待も使えるの?
2019.12.162022.07.26
Tweets by moneyhensachi
© 2019-2022 日常的マネー偏差値向上ブログ.
LINEMO(ラインモ)4
ドラム式洗濯乾燥機11
日本郵便・ゆうちょ40
【1番高いのは?】マクドナルドの株主優待でお得な最高値商品を頼みたい!どれが頼める?2022年9月
【2022年9月】すきパス(Sukipass)で対象外の商品は? ランチセット・スキミックス・うな丼・うな牛・期間限定にも使える?
【反応しない】Fire TV Stickリモコンで電源・音量ボタンが効かない・動作しない場合の設定方法。
2021.04.122021.08.03
【比較】バーガーキングのワッパーとワッパージュニアとは?マクドナルドのハンバーガーと比べてみた 大きさ・価格の違い
2022.05.092022.09.06
【どこで買える?】PayPayギフトカードとは。コンビニで購入できる?購入場所や購入方法は?
【2022年9月】auスマートパスプレミアム(スマプレ)の特典・クーポンまとめ 今は入るとお得?Relux・ケンタッキー・マクドナルド
2022.09.032022.09.07
【2022年~2023年版】宝島社 ぴあ 飲食店ファンブックまとめ。おすすめ3選! 今買うならこれ!元を取りやすいのは?
2022.07.102022.07.11
【2022年9月後半】お得な日はいつ?いつ買う? ヤフーショッピングとPayPayモールの買い時とキャンペーンまとめ。
2022.09.162022.09.24
【履歴消去】Amazonプライムビデオの履歴を消したい。テレビ・スマホ・アプリから削除する方法まとめ。


langs: ja ja ja ja en ja ja ja ja zh ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja zh ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja nl als ja zh ja ja ja ja zh ja ja ja zh ja ja ja ja ja ja zh ja ja ja ja ja ja ja ja ja ja ja ja ja ja nl als zh ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja

text:
リモートワークに関する海外書籍を読んでみた③「The Year Without Pants: WordPress.com and the Future of Work」 | リモートワークラボ
(最終更新 2018/08/14)
日本ではまだまだリモートワークについて書かれた書籍は多くありません。そこでリモートワークラボでは、海外で出版されている書籍の中から、リモートワークを実践するにあたって参考になりそうな書籍を選んで、研究員が読破し感想とともにご紹介しています。ぜひご参考に。
500人以上の会社でもリモートワークは可能
この本は作者のScott Berkunさんが、WordpressというブログのソフトウェアをつくっているAutomatticという会社で働いた時の実体験を元に書かれています。
WordPressといえば、全世界のサイトの約30%はwordpressを使ってできていると言われているほど世界的に存在感の大きいソフトウェアです。そんなAutomattic社はそのユニークな社風で有名で、ほぼ全社員がリモートワーク、成果主義でいつ、どれだけの時間働くのかも自由、締め切りなし、スケジュール管理もなしで、もちろん服装も自由です。(この本の題名はここからきています。笑)社内のコミュニケーションは全体を100%とすると社内ブログが75%、IRC(チャット)が14%、Skypeが5%、メールがなんと1%という割合になっており、一般的な会社とは大きく異なることがわかります。
この本ではAutomatticがどのようにして運営されているのか、実際にAutomatticで働いた作者にしか語れない日々の業務の雰囲気から新人教育といった細部まで語られています。
題名からもわかる通り、カジュアルな文体で書かれており、読み物としても面白かったです。
2点感想があり、1点目はリモートワークに限らず、自由な社風の会社にとって採用というのがとても大切になってくるんだなと思いました。
規則がなく、自由な環境で働くにはセルフマネジメントが欠かせません。Automatticでは、面接や履歴書で判断することはしません。応募者がセルフマネジメントができる人か、能力があるかを見極めるため、トライヤルで実際にプロジェクトに入り、働きぶりを評価されます。
これは採用までにとても時間がかかるため、応募する側にとっても会社側にとっても大変なことですが、深くお互いのことを知るためには、一緒に働いてみるというのが実は一番の近道なのではないかと思います。
500人以上の会社でもリモートワークは可能
2点目は、500人以上の社員を抱えるAutomatticが全社員リモートワークを実現させているという事実は、リモートワークを研究している私たちにとって大きなことだと思いました。
というのもリモートワークは「小さな会社だから実現できる」「大きな会社は社員の目が届かなくなるのでリモートワークは無理」と言われることがありますが、会社の規模や世界的影響力からみて間違いなく大企業であるAutomatticがこうして完全リモートワークを実現している姿をみていると、ポイントは会社の規模ではなくやり方なのだとこの本が語りかけてきている気がしました。
野本 司(のもと つかさ)
リモートワーク研究所研究員。大学時代にアメリカとスウェーデンでの留学を経験後、2017年4月にソニックガーデンに入社。入社前のインターンシップから現在まで、ずっとリモートワークをしている。世界各地を移住しながら働く生活スタイルに憧れがあり、現在計画進行中。趣味は旅行、プログラミング、野球。
リモートワークラボをフォローしよう。
リモートワークラボにいいね!する リモートワークラボをフォローする
リモートワークに関する海外書籍を読んでみた①「Working Remotely the telecommunications to the galaxy」
リモートワークに関する海外書籍を読んでみた②「Your Brain at Work」
海外に行ったら一度は訪れたい!異空間を演出する世界のコーワーキングスペース
リモートワークラボがオススメする海外ブログ5選
リモートワークに関する海外書籍を読んでみた①「Working Remotely the telecommunications to the galaxy」
リモートワークに関する海外書籍を読んでみた②「Your Brain at Work」
海外に行ったら一度は訪れたい!異空間を演出する世界のコーワーキングスペース
リモートワークラボがオススメする海外ブログ5選
お役立ちダウンロード資料
「オフィスワークだけの会社」が「リモートワークが普通な会社」に変化するためのステップ、リモートワークで必要となるツールを紹介します。