覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

「Clean Architecture 達人に学ぶソフトウェアの構造と設計」

かなり前に読み終えてはいたんですが、「Clean Architecture 達人に学ぶソフトウェアの構造と設計」の内容に関する今更ながらの自分用のメモです。

結構話題になった本だと思います。自分にとっても結構役に立つ内容が多かったです。
(ただ、まだ若干理解しきれない部分もありましたが。。。)


書籍の内容紹介には以下のように書かれています

アーキテクチャのルールはどれも同じである!

書いているコードが変わらないのだから、どんな種類のシステムでもソフトウェアアーキテクチャのルールは同じ。
ソフトウェアアーキテクチャのルールとは、プログラムの構成要素をどのように組み立てるかのルールである。
構成要素は普遍的で変わらないのだから、それらを組み立てるルールもまた、普遍的で変わらないのである。
(本書「序文」より)


この本は、Clean Architecture という考え方に言及したのものですが、具体的な実装方法については記述されていない点には注意が必要です。
あくまで概念的な説明や抽象的な内容で、大規模ソフトウェア(長期的に運用するアプリケーション)を設計する際に気を付けなければならないポイントに焦点を当てて解説してくれています。
(※パッケージ構成やクラス間の依存関係について説明のある章はあります)

そのため、プログラムの初学者や未経験者が読むには適していません。
一定の経験を積んだソフトウェア開発者が読むのが良いと思われます。
プログラム初学者がいきなり手を出すと、読んでいる最中に眠くなるんじゃないかな・・と思います。


章立ては以下のようになっています。

  • 第1章: 設計とアーキテクチャ
  • 第2章: 2つの価値のお話
  • 第3章: パラダイムの概要
  • 第4章: 構造化プログラミング
  • 第5章: オブジェクト指向プログラミング
  • 第6章: 関数型プログラミング
  • 第7章: SRP : 単一責任の原則
  • 第8章: OCP : オープン・クローズドの原則
  • 第9章: LSP : リスコフの置換原則
  • 第10章: ISP : インターフェィス分離の原則
  • 第11章: DIP : 依存性関係逆転の原則
  • 第12章: コンポーンネント
  • 第13章: コンポーネントの凝集性
  • 第14章: コンポーネントの結合
  • 第15章 : アーキテクチャとは?
  • 第16章 : 独立性
  • 第17章 : バウンダリー:境界線を引く
  • 第18章 : 協会の解剖学
  • 第19章 : 方針とレベル
  • 第20章 : ビジネスルール
  • 第21章 : 叫ぶアーキテクチャ
  • 第22章 : クリーンアーキテクチャ
  • 第23章 : プレゼンターとHumble Object
  • 第24章 : 部分的な境界
  • 第25章 : レイヤーと境界
  • 第26章 : メインコンポーネント
  • 第27章 : サービス:あらゆる存在
  • 第28章 : テスト境界
  • 第29章 : クリーン組込みアーキテクチャ
  • 第30章 : データベースは詳細
  • 第31章 : ウェブは詳細
  • 第32章 : フレームワークは詳細
  • 第33章 : 事例:動画販売サイト
  • 第34章 : 書き残したこと

各章は、内部的にさらに細かな章で構成されています。
その最も細かく分類された章1つ1つをとって見ると別の書籍やウェブサイトですでに一般的に語られている内容が書かれていることも少なくないです。
この本は、基本的には本全体を通して読む必要があると思います。が、各章単体で見ても、ソフトウェア設計における重要な要素を示唆している場合もあります。

個人的には、第21章の「叫ぶアーキテクチャ」が好きです。
この章はものすごく短い内容ですが、大規模ソフトウェア(システム・サービス・アプリケーション)で、これが出来ていない、または、修正を繰り返すうちに、これが出来なくなっているというケースが少なくないなーという重要な指摘がされています。

他にも 第32章 : フレームワークは詳細 なども面白いです。


各章の詳細は、この本を読んでもらって把握してもらいたいのですが、
本書籍内で、私が気になった部分の抜粋を一部メモしておきます。(括弧部分は私の補足です)


  • ソフトウェアアーキテクチャの目的は、求められるシステムを構築・保守するために必要な人材を最小限に抑えることである
  • 設計の品質は、顧客のニーズを満たすために必要な労力で計測できる。必要な労力が少なく、システムのライフタイム全体で低く保たれているならば、その設計は優れている。逆にシステムごとに労力が増えるなら、その設計は優れていない。
  • 「あとでクリーンにすればいいよ。先に市場に出さなければ!」
    開発者たちはそうやっていつもごまかす。だが、あとでクリーンにすることはない。市場からのプレッシャーは止まらないからだ。「先に市場に出さなければ」ということは、後ろに競合他社が大勢いるということである。競合他社に追い抜かれないためには、これからも走り続けるしかない。
  • 崩壊したコードを書くほうがクリーンなコードを書くよりも常に遅い。
  • ソフトウェアはソフトでなければならない。つまり、簡単に変更できなければならない。ステークホルダーが機能を変更したいと思えば、その変更は簡単にできるようになっておくべきだ。変更の難易度は、変更の形状ではなく、変更スコープに比例しなければならない。
  • ビジネスマネージャーに変更を希望するかと聞くと、当然ながら「希望する」と答える。ただし、将来の柔軟性よりも現在の機能性の方が重要だと回答を補足するはずだ。とはいえ、ビジネスマネージャーが変更を求めたとき、あながた膨大なコストを見積もると、変更が非現実的になるまでシステムを放置したあなたに対して、ビジネスマネージャーは激怒するだろう。
  • これらのパラダイム(構造化プログラミング、オブジェクト指向プログラミング、関数型プログラミング)は、プログラマから能力を削除しているのである。新しい能力を提供しているものはない。それぞれがネガティブな意図を持ち、何らかの規律を課しているのである。これらのパラダイムは、何をすべきかを伝えるというよりも、何をすべきでないかを伝えているのである。
  • ソフトウェア開発は、数学的な構成要素を操作しているかのように思えるかもしれないが、実際には数学的な試みではない。むしろソフトウェア開発は科学のようなものである。どれだけ最善を尽くしても正しくないことを証明できないことによって、その正しさを明らかにしているのである。
  • 確かにOO(オブジェクト指向)は、プログラマがカプセル化されたデータを盗み見ないように、礼儀正しく振舞うべきであるかという考え方に依存している。だとしても、OOを提供すると主張している言語は、C言語で完ぺきに実現できているカプセル化を弱体化させてしまっているのである。
  • ポリモーフィズムは関数へのポインタの応用である。1940年代後半にノイマン型アーキテクチャが実装されて以来、プログラマはポリモーフィズムの振る舞いを実現するために、関数へのポインタを使用してきた。つまり、OO(オブジェクト指向)は新しいものを提供しているわけではないのである。
  • アーキテクチャの観点からすると、なぜこれ(不変性)が重要なのだろうか?なぜアーキテクトは、変数の可変性に配慮すべきなのだろうか?その答えは、簡単すぎるぐらいである。それは、競合状態、デッドロック状態、並行更新の問題の原因が、すべて可変変数にあるからだ。変数が変化しないのであれば、競合状態や並行更新の問題は発生しない。また、変更可能なロックがなければ、デッドロックになることもない。
  • モジュールはたったひとつのアクターに対して責務を負うべきである。
  • 変化しやすい具象クラスを継承しない。具象関数をオーバーライドしない。
  • 最終的にどのような形式でデプロイするかにかかわらず、よくできたコンポーネントは常に個別にデプロイできる状態を保っているため、個別に開発を進められる。
  • 多くのアプリケーションにおいて、再利用性よりも保守性の方が重要だ。アプリケーションコードが変更しなければならないときには、ひとつのコンポーネントに変更箇所がまとまっているほうが、あちこちのコンポーネントに散らばっているよりもありがたい。変更箇所がひとつのコンポーネントに閉じていれば、変更後にデプロイする必要があるのはそのコンポーネントだけになる。そのコンポーネントに依存していないコンポーネントは再デプロイする必要がない。
  • コンポーネントの依存構造をきちんと管理しておく必要がある。循環依存があってはいけない。
  • 循環依存があると、コンポーネントを切り離すのが難しくなる。ユニットテストやリリースも難しくなり、失敗に陥りやすくなる。さらに、モジュールの数が増えるにつれて、ビルド時の課題も幾何級数的に増加する。
  • アーキテクチャの主な目的は、システムのライフサイクルをサポートすることである。優れたアーキテクチャがあれば、システムを容易に理解・開発・保守・デプロイできる。最終的な目的は、システムのライフタイムコストを最小限に抑え、プログラマの生産性を最大にすることである。
  • ソフトウェアシステムが効果を生み出すためには、デプロイ可能な状態でなければいけない。デプロイのコストが高ければ、どの分だけシステムの有用性は低下する。ソフトウェアアーキテクチャの目的、システムを単一のアクションで簡単にデプロイできるようにすることである。
  • ソフトウェアシステムのすべてにおいて、保守は最もコストがかかるものである。永遠に続く新しい機能の登場と、避けられない欠陥や修正の繰り返しは、人的リソースを膨大に消費する。...
    アーキテクチャを慎重に考え抜けば、これらのコストは大幅に低下する。システムをコンポーネントに分離して、安定したインターフェイスを持つ独立したコンポーネントにしておけば、将来の機能の道筋が照らされ、意図せずに壊してしまうリスクを大幅に低減できるだろう。
  • 明らかに重複しているコードが異なる進化を遂げ、数年後には両者がまるで違ったものになっていることもある。これは本物の重複ではない。偽物の重複、あるいは偶然の重複である。
  • レイヤーを水平に分離していると、あるデータベースのレコードのデータ構造が、ある画面のデータ構造とよく似ていることに気づくことがある。ビューモデルを作成して要素をコピーするのではなく、データベースのレコードをそのままUIに渡したくなるが、注意してほしい。これは確実に偶然の重複である。ビューモデルを作成するのは大した労力ではないし、レイヤーを適切に切り離すのに役立つだろう。
  • 早すぎる決定とはどのようなものなのか?それは、システムのビジネス要件(ユースケース)と関係のない決定である、たとえば、フレームワーク、データベース、ウェブサーバ、ユーティリティライブラリ、DIなどに関する決定が含まれる。優れたシステムアーキテクチャは、このような決定を従属的かつ遅延可能なものにする。優れたアーキテクチャは、このような決定に依存しない。優れたシステムアーキテクチャは、重大な影響を与えることなく、このような決定を最終時点まで引き延ばせる。
  • アーキテクチャの目標は、下位レベルのプロセスを上位レベルのプロセスのプラグインにすることである。
  • ユースケースとは、自動化されたシステムを使用する方法を記述したものである。ユーザから提供された入力、ユーザに戻す出力、出力を生成する処理ステップなどを規定している。エンティティに含まれる最重要ビジネスルールとは違い、ユースケースにはアプリケーション固有のビジネスルールを記述している。
  • あなたのアプリケーションのアーキテクチャは何と叫んでいるだろうか?最上位レベルのディレクトリ構造と最上位レベルのパッケージのソースファイルは、「ヘルスケアシステム」「会計システム」「在庫管理システム」と叫んでいるだろうか?それとも「Rails」 「Spring/Hibernate」「ASP」と叫んでいるだろうか?
  • アーキテクチャはフレームワークに関するものではない。アーキテクチャはフレームワークから提供されるものではない。フレームワークは使用するツールであり、アーキテクチャが従うものではない。あなたのアーキテクチャがフレームワークにもとづいているのなら、そのアーキテクチャはユースケースにもとづくことはできない。
  • アーキテクチャは、システムで使用しているフレームワークではなく、システムそのものについての情報を伝える必要がある。たとえば、ヘルスケアシステムを構築しているのであれば、新しく参加したプログラマがソースリポジトリを見たときに、「ああ、これはヘルスケアシステムだ!」と思えるようにしておくべきである。システムの提供方法はまだわからなくても、システムのユースケースを全て把握できるようにしておくべきだ。
  • Mainコンポーネントは、究極的な詳細である。システムの最初のエントリポイントとなる。オペレーティングシステム以外に、このコンポーネントに依存しているもはない。FactoryやStrategyなどのグローバルな要素を作成し、システムの上位の抽象部分に制御を渡すことが、このコンポーネントの仕事になる。
    依存関係については、このMainコンポーネントにDIフレームワークを使って注入する必要がある。Mainに注入できたら、あとはフレームワークを使用せずに、Mainが通常のやり方で依存関係をちりばめる。
    Mainは、汚れ仕事が最も似合うコンポーネントだ。
  • ソフトウェア設計の第一のルールは、その理由がテスト容易性だろうと何だろうと、常に同じである。それは、変化しやすいものに依存しないだ。GUIは変化しやすい。GUIを使用してシステムを操作するテストスイートは脆弱である。したがって、GUIを使用しなくてもビジネスルールのテストができるように、システムとテストを設計しなければならない。
  • データ構造であるデータモデルは、アーキテクチャ的には重要である。だが、回転する磁気面上でデータを移動させる技術やシステムは、アーキテクチャ的には重要ではない。リレーショナルデータベースシステムは、データを表形式で管理して、SQLからアクセスするための仕組みであり、これはデータモデルよりも技術やシステムに近い。データこそが重要なのであり、データベースは詳細なのである。
  • (フレームワークの)作者にとって、フレームワークとの結合には何のリスクもない。むしろフレームワークとの結合を望んでいる。作者自身はそのフレームワークを好きなようにコントロールできるからだ。
    さらに作者は、あなたに対してもフレームワークとの結合を望んでいる。一度でも結合してしまえば、切り離すのはとてもむずかしくなるからだ。フレームワークの作者にとって、自分の作った基底クラスを大勢のユーザが継承することほど、自尊心が満たされることはない。

このメモをは自分用のものなので、これだけ読んでも何のこっちゃ という感じだと思いますので、
ぜひぜひ書籍を手にとって読んでもらいたいです。



関連リンク

「ザッソウ 結果を出すチームの習慣 ホウレンソウに代わる「雑談+相談」」

「ザッソウ 結果を出すチームの習慣 ホウレンソウに代わる「雑談+相談」」 を読んでみました。

ザッソウ 結果を出すチームの習慣

ザッソウ 結果を出すチームの習慣

  • 作者:倉貫義人
  • 発売日: 2019/08/30
  • メディア: Kindle版


内容紹介は以下のようになっています

●「ホウレンソウ」だけではチームは回らない
仕事をする上で、「ホウレンソウ(報告・連絡・相談)」は大切です。
こまめな報告があれば安心でき、
連絡が行き届くことで無駄もなくなり、
相談があることでいち早く問題を解決することができます。
ホウレンソウは社会人にとっての基礎スキルといえます。

ただ、ホウレンソウだけでは、
チームのコミュニケーションが機能しなくなってきています。

近年、チーム間で(とくに、上司と部下の間で)個人的な話がしにくくなっています。
働き方改革によって残業が減り、飲みニケーションや喫煙所での会話も少なくなりました。
ハラスメントに注意しすぎて仕事以外の話もしにくく、
つねに成果を求められているため、短期的な仕事の話が中心になっています。

こうしたコミュニケーションだけでは、
人を、成果を出すための道具としてしか見られなくなり、
やがてチームのモチベーションは下がっていってしまいます。
人としてコミュニケーションがとれる場を、
チームとして継続的に設けることが必要なのです。

●チームを活性化させる「ザッソウ」
具体的にいうと、それはチームにおけるコミュニケーションのあり方を
「ホウレンソウ」のステージから「ザッソウ(雑談・相談)」に上げる、ということです。

ザッソウを通して、メンバー同士が何を考え、
何を感じているのかを共有し、言いたいことを言い合える信頼関係をつくる。
それはチームに心理的安全をもたらし、メンバーのやる気を高めることにもつながります。

それに普段から雑談をしていれば、本当に困ったときに相談しやすくなります。
旧来のホウレンソウだけの状態では、信頼関係ができていないわけですから、
たとえ「いつでも相談していい」と言われていても、なかなか声をかけにくいものです。
つまり話しかける心理的ハードルを下げるためにも、ザッソウは有効なのです。

それだけではありません。新しいアイデアが出ない、専門的な知識がなくて困っている……
そんな問題を解決したいとき、ザッソウでコミュニケーションをとるうちに
価値が生まれることがあります。
ザッソウは、イノベーションにつながるアイデアが生まれ、
チームの生産性を高めることにもつながるのです。


本のカバーには以下のようにザッソウが定義されています。

  • ざっそう【ザッソウ】
    • 「雑談+相談」「雑な相談」
    • 雑談があることで相談がしやすくなり、人間関係が構築されて心理的安全性を高めることができる。
      チームビルディングを成功させるのはホウレンソウではなくザッソウ。


社会人になると、早い段階でまず耳にするであろうホウレンソウ(報・連・相)。
報告、連絡、相談 というのを適切なタイミングでやることを社会人として求められます。
なんとなく、部下が身につけておくべき基本的な姿勢という風に思われます。

が、実際のところ

そもそも「報連相」とは、山種証券(現・SMBCフレンド証券)社長の山崎富治氏が社内で「ほうれんそう運動」を始めたことがきっかけです。

下からの意見をどう吸いあげるか、みんなが働きやすい環境をどう作るか、暖かい人間関係をどう作るか、
少数精鋭で社員一人ひとりに厚く報いるには…と、
つね日ごろ頭を悩ませていたとき思いついたのが、”ほうれんそう”だった。

上司・部下の間で情報が行き来する風通しの良い職場環境を作ろうというスローガンで、部下への情報共有を義務付けるものではありません。

しかし、現在ではなんとなく部下への義務的な要件として浸透しています。

世間一般的に広まった ホウレンソウ ではなく ザッソウ によって
組織内の相互理解が高まる、心理的安全性が生まれる、率直に意見をぶつけ合えることで成果が出せる、
新しいアイデアが生まれる、組織を変える土台になる、、、ということが述べられています。

筆者が関わってきた事例などをベースにザッソウの重要性や必要性が述べられています。
割と薄い本ですが、内容は共感する部分が多いです。
多くの人に読んでもらいたい本です。


以下は私が書籍内で気になった部分の抜粋です。(自分用のメモです)

  • 1on1ミーティング という形で、上司と部下で評価面談と別にお互いに考えていることなどを共有する機会を作っている組織が多くあります。
    この1on1ミーティングこそ、中身はザッソウです。雑談であったり、相談であったり、特に決着をつけなくていいからこそ、気軽に話をすることができます

  • 気軽なフィードバックで仕事の質と速度が向上する

    • マネージャーや上司として困るのは、依頼や指示をした内容とまったく違うものが時間をかけた後に出てくるケースです。
    • 少しずつ確認していくことが手戻りを減らす最善手となります
  • 働きがいと働きやすさが両立したワクワクする職場には、事業を自分事として考えられる人たちがいる活気の良さと、経済的に安心できて家族や自分の時間を持つことができる制度があります。

  • 少なくとも手応えがあることは、働きがいの必須条件といえます。
    それはどのような形であれフィードバックがあることです。
    半年に一度の評価面談や定例会議などを持つことなく、気軽にザッソウしてフィードバックされる気合があればいいのです。

  • 心理的安全性を高める9つの観点

    • チームの目標がはっきりしている
    • 適度に対話しやすい人数である
    • 強みを知り、認め合っている
    • 強みだけでなく、弱みも見せる
    • プライベートなことも共有している
    • 情報がオープンになっている
    • 判断基準と価値観が共有されている
    • リアクションの意識がそろっている
    • 「肯定ファースト」と「NOと言うこと」
  • 自分の弱みを隠さないでいられれば、困ったときに気軽にザッソウできるようになるので成果も上げることができています。
    チームの利点は、仲間が集まることで、1人では実現できない挑戦ができることです。一方、弱ったときに助け合うことで、
    1人で潰れてしまわないようにするという利点もあります。そもそも助け合うためには、弱みを見せることは悪いことでは無いのです。

  • フォロワーもある意味でリーダーシップの1つの形態であると主張しています。

  • ザッソウあふれる職場やチームには、明るく穏やかな空気が流れています。
    その雰囲気を生み出しているのは、やはりリーダーの姿勢や人柄によるところが大きくなります。

  • しなやかなマインドセットの人には、人間の基本的な資質は努力次第で伸ばすことができるという信念があります。
    持って生まれた資質は違っていても、その後の努力と経験で伸ばすことができる。
    だから、現時点で足りていない・できていない自分でも認めることができるのです。ですから難しい問題に出会っても粘り強く立ち向かうことができます。

  • 自己組織化とは、もともと自然現象をモデル化した際に使われた表現で、自律的に秩序や構造を作り出す現象のことをいいます。
    それを組織やチームへ適用して考えてみると、組織を構成しているメンバーそれぞれが、場に備わったミッションや目的を達成するために、自律的に考えて行動しつつも相互に助け合い、作用し合っている状態と考えられます。

  • ネットワーク型の情報共有は、メンバー1人ひとりの情報リテラシーが問われます。情報を自ら収集する必要がありますし、その情報の正しさを精査して理解するところは各人に任されるからです。しかし、そうして分散化した方がスピードが速く、新しく価値が生み出される可能性すらあるのです。それはまさしくインターネットの世界で実証されています。

  • 人というのは、どれだけ外部から良い方法を教えたり、指導したところで行動を変えることは難しいものです。それは自分たちのことがわかっていないからです。必要なのは、自分たちのことを知る機会です。自分たちが本当にまずい状態だと思えば改善する意欲もわくでしょう。そうなって、ようやく変わる出発点に立ってくれます。鏡を見ることがなければ、自分の顔が汚れていることには気づかないものです。だから、ふりかえりとは、チームで自分たちの姿を鏡で見る機会でもあるのです。

矩形同士の交差

左下の座標(X, Y) と 右上の座標(X, Y) が 与えられた矩形(長方形)があったとして、

f:id:nini_y:20190829212551p:plain

2つの矩形が与えられて、その矩形同士が以下のように交差(領域が被っているかを)しているか判定したいです

f:id:nini_y:20190829230458p:plain

交差の判定は

Max(矩形1.左下X, 矩形2.左下X) < Min(矩形1.右上X, 矩形2.右上X) かつ Max(矩形1.左下Y, 矩形2.左下Y) < Min(矩形1.右上Y, 矩形2.右上Y)

で、出来ます。(矩形同士の線分が接しているだけの状態は交差とは呼びにくいと思うので、<= ではなく < で判定しています


一応Javaでプログラム書いてみました。
なんか中途半端な気もしますが・・・

/**
 * 矩形を表現するクラス
 */
public final class Rectangle {

    /** 矩形の左下の点のX */
    private final int lBottomX;

    /** 矩形の左下の点のY */
    private final int lBottomY;

    /** 矩形の右上の点のX */
    private final int rTopX;

    /** 矩形の右上の点のY */
    private final int rTopY;

    public Rectangle(int lBottomX, int lBottomY, int rTopX, int rTopY) {
        // そもそも線や点の表現になってしまう場合は矩形とみなさないものとする
        // 左下と右上の位置関係にならない場合も不正とみなす
        if (lBottomX >= rTopX || lBottomY >= rTopY) {
            throw new IllegalArgumentException("Invalid parameter.");
        }

        this.lBottomX = lBottomX;
        this.lBottomY = lBottomY;
        this.rTopX = rTopX;
        this.rTopY = rTopY;
    }

    /**
     * 引数の矩形と2点が交差するかどうかを判定します
     *
     * @param rec 判定対象の矩形
     * @return 交差する場合true, それ以外の場合false
     */
    public boolean hasIntersect2point(Rectangle rec) {
        return Math.max(lBottomX, rec.lBottomX) < Math.min(rTopX, rec.rTopX) &&
                Math.max(lBottomY, rec.lBottomY) < Math.min(rTopY, rec.rTopY);
    }

    /**
     * 2点が交差する場合の交差している領域を表現する矩形を返します
     *
     * @param rec 判定対象の矩形
     * @return 交差している領域を表現する矩形
     */
    public Rectangle intersect2pointRectangle(Rectangle rec) {
        int maxLBottomX = Math.max(lBottomX, rec.lBottomX);
        int maxLBottomY = Math.max(lBottomX, rec.lBottomY);
        int minRTopX = Math.min(rTopX, rec.rTopX);
        int minRTopY = Math.min(rTopY, rec.rTopY);

        if (!hasIntersect2point(maxLBottomX, minRTopX, maxLBottomY, minRTopY)) {
            throw new IllegalStateException("not intersect 2point.");
        }

        return new Rectangle(maxLBottomX, maxLBottomY, minRTopX, minRTopY);
    }

    private boolean hasIntersect2point(int maxLBottomX, int minRTopX, int maxBottomY, int minRTopY) {
        return maxLBottomX < minRTopX && maxBottomY < minRTopY;
    }

    /**
     * 面積を求める
     *
     * @return 面積
     */
    public int area() {
        return (rTopX - lBottomX) * (rTopY - lBottomY);
    }

    @Override
    public String toString() {
        return "Rectangle{" +
                "lBottomX=" + lBottomX +
                ", lBottomY=" + lBottomY +
                ", rTopX=" + rTopX +
                ", rTopY=" + rTopY +
                ", area=" + area() +
                '}';
    }
}


交差していない矩形同士の場合

矩形同士が離れている場合

public class Main {
    public static void main(String[] args) {
        Rectangle rBase = new Rectangle(2, 2, 6, 5);

        Rectangle r1a = new Rectangle(0, 0, 1, 1);

        System.out.println(""rBase.hasIntersect2point(r1a));        
    }
}


出力結果

false

交差の判定が false になっています


矩形同士のある1辺だけが接していて領域としては重なっていない場合

public class Main {
    public static void main(String[] args) {
        Rectangle rBase = new Rectangle(2, 2, 6, 5);

        Rectangle r1b = new Rectangle(6, 3, 9, 8);

        System.out.println(""rBase.hasIntersect2point(r1b));        
    }
}


出力結果

false

2つの矩形のある辺が重なっている場合も、交差の判定は false になっています


交差する矩形同士の場合

public class Main {
    public static void main(String[] args) {
        Rectangle rBase = new Rectangle(2, 2, 6, 5);

        Rectangle r2 = new Rectangle(1, 1, 3, 4);

        System.out.println(rBase.hasIntersect2point(r2));
        System.out.println(rBase.intersect2pointRectangle(r2));
    }
}


出力結果

true
Rectangle{lBottomX=2, lBottomY=2, rTopX=3, rTopY=4, area=2}


public class Main {
    public static void main(String[] args) {
        Rectangle rBase = new Rectangle(2, 2, 6, 5);

        Rectangle r3 = new Rectangle(3, 3, 4, 4);

        System.out.println(rBase.hasIntersect2point(r3));
        System.out.println(rBase.intersect2pointRectangle(r3));
    }
}


出力結果

true
Rectangle{lBottomX=3, lBottomY=3, rTopX=4, rTopY=4, area=1}


public class Main {
    public static void main(String[] args) {
        Rectangle rBase = new Rectangle(2, 2, 6, 5);

        Rectangle r4 = new Rectangle(3, 3, 6, 7);

        System.out.println(rBase.hasIntersect2point(r4));
        System.out.println(rBase.intersect2pointRectangle(r4));
    }
}


出力結果

true
Rectangle{lBottomX=3, lBottomY=3, rTopX=6, rTopY=5, area=6}


交差の判定が true になって、交差した領域の矩形の情報(座標情報, 面積)が出力されます。

jqで基礎的な操作をしてみる

前回、jqコマンドをインストールしました。


基礎的な操作だけ試しておきます。

仮に person.json というファイルの内容が以下のようになっているとします

{ "name": { "first" : "taro", "last": "yamada" }, "age": 20 }


これを整形して表示する場合は以下になります

cat person.json | jq .

出力結果は以下の通りで整形されています。

{
  "name": {
    "first": "taro",
    "last": "yamada"
  },
  "age": 20
}


このJSONから「name」項目を取得したい場合は以下です

cat person.json | jq .name

出力結果は以下の通りで整形されています。

{
  "first": "taro",
  "last": "yamada"
}


「name」の中の 「first」の値だけ取りたいときは以下になります

cat person.json | jq .name

出力結果は以下の通り

"taro"


このやり方が望ましいかわかりませんが以下でもいけますね

cat person.json | jq .name | jq .first

出力結果は以下の通り

"taro"


出力結果を囲んでいるダブルクォートが邪魔な場合は -r オプションをつけます

cat person.json | jq -r .name

出力結果は以下の通り

taro


「name.first」 と 「name.last」 を結合した結果を出力してみます

cat person.json | jq .name | jq -r '.first + " " + .last'

出力結果は以下の通り

taro yamada


少し複雑めなJSONを操作

$ curl -s "http://geoapi.heartrails.com/api/json?method=searchByPostal&postal=1060032" | jq . 
{
  "response": {
    "location": [
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木一丁目",
        "town_kana": "ろっぽんぎ1ちょうめ",
        "x": "139.740991",
        "y": "35.665082",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木二丁目",
        "town_kana": "ろっぽんぎ2ちょうめ",
        "x": "139.737087",
        "y": "35.666974",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木三丁目",
        "town_kana": "ろっぽんぎ3ちょうめ",
        "x": "139.735452",
        "y": "35.663977",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "ç": "六本木四丁目",
        "town_kana": "ろっぽんぎ4ちょうめ",
        "x": "139.733837",
        "y": "35.665489",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木五丁目",
        "town_kana": "ろっぽんぎ5ちょうめ",
        "x": "139.735248",
        "y": "35.658358",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木六丁目",
        "town_kana": "ろっぽんぎ6ちょうめ",
        "x": "139.729932",
        "y": "35.659856",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木七丁目",
        "town_kana": "ろっぽんぎ7ちょうめ",
        "x": "139.726863",
        "y": "35.664751",
        "prefecture": "東京都",
        "postal": "1060032"
      }
    ]
  }
}


上記の中から「town」だけ列挙してみる

curl -s "http://geoapi.heartrails.com/api/json?method=searchByPostal&postal=1060032" | jq .response.location[].town
"六本木一丁目"
"六本木二丁目"
"六本木三丁目"
"六本木四丁目"
"六本木五丁目"
"六本木六丁目"
"六本木七丁目"


当初取得した値の中から 「town」,「x」, 「y」だけのJSONにしたい場合は map 使うと可能です

$ curl -s "http://geoapi.heartrails.com/api/json?method=searchByPostal&postal=1060032" | jq '.response.location | map({town: .town, x: .x, y: .y})'
[
  {
    "town": "六本木一丁目",
    "x": "139.740991",
    "y": "35.665082"
  },
  {
    "town": "六本木二丁目",
    "x": "139.737087",
    "y": "35.666974"
  },
  {
    "town": "六本木三丁目",
    "x": "139.735452",
    "y": "35.663977"
  },
  {
    "town": "六本木四丁目",
    "x": "139.733837",
    "y": "35.665489"
  },
  {
    "town": "六本木五丁目",
    "x": "139.735248",
    "y": "35.658358"
  },
  {
    "town": "六本木六丁目",
    "x": "139.729932",
    "y": "35.659856"
  },
  {
    "town": "六本木七丁目",
    "x": "139.726863",
    "y": "35.664751"
  }
]


まとめ

まとめるほど触ってませんが、まだまだ相当な機能やオプションをjqコマンドは備えています。
もっと使いこなせるようになりたいです。



関連エントリ

HomeBrew で jqをインストールしてみる

jsonデータを整形・絞り込みできるjqコマンドをmacOSにインストールしたかったので、HomeBrewでインストールしてみました。


インストール

以下を実行します。

$ brew install jq


実行時のログは以下の様な感じでした

$ brew install jq
==> Installing dependencies for git: pcre2
==> Installing git dependency: pcre2
==> Downloading https://homebrew.bintray.com/bottles/pcre2-10.32.mojave.bottle.tar.gz
Updating Homebrew...
######################################################################## 100.0%
==> Pouring pcre2-10.32.mojave.bottle.tar.gz
🍺  /usr/local/Cellar/pcre2/10.32: 224 files, 5.5MB
==> Installing git
==> Downloading https://homebrew.bintray.com/bottles/git-2.19.2.mojave.bottle.tar.gz
######################################################################## 100.0%
==> Pouring git-2.19.2.mojave.bottle.tar.gz
==> Caveats
Bash completion has been installed to:
  /usr/local/etc/bash_completion.d

zsh completions and functions have been installed to:
  /usr/local/share/zsh/site-functions

Emacs Lisp files have been installed to:
  /usr/local/share/emacs/site-lisp/git
==> Summary
🍺  /usr/local/Cellar/git/2.19.2: 1,520 files, 40.1MB
==> Caveats
==> git
Bash completion has been installed to:
  /usr/local/etc/bash_completion.d

zsh completions and functions have been installed to:
  /usr/local/share/zsh/site-functions

Emacs Lisp files have been installed to:
  /usr/local/share/emacs/site-lisp/git
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
Warning: Aleth (formerly cpp-ethereum) has been removed from Homebrew. Please install binary releases from https://github.com/ethereum/aleth/releases.
==> Auto-updated Homebrew!
Updated 3 taps (homebrew/core, homebrew/cask and ethereum/ethereum).
==> New Formulae

(中略)

==> Renamed Formulae
ark -> velero                              gnatsd -> nats-server                      todolist -> ultralist
confluent-oss -> confluent-platform        php72 -> php@7.2                           transmission -> transmission-cli
gloo-ctl -> glooctl                        resin-cli -> balena-cli
==> Deleted Formulae
apple-gcc42          gdnsd                js-test-driver       pdftoedn             rock                 tomcat@6
at-spi2-atk          gnome-doc-utils      ld64                 percona-server@5.6   ruby@1.8             typesafe-activator
at-spi2-core         go@1.4               libggz               php@5.6              ruby@2.3             varnish@4
cctools              go@1.8               libguess             php@7.0              safe                 whirr
cctools-headers      gradle@2.14          liblastfm            plan9port            scala@2.10           xmoto
compose2kube         gtk-engines          libutf               pldebugger           smlnj                zxing-cpp
cputhrottle          gtk-murrine-engine   lysp                 protobuf@2.5         solr@5.5
dsd                  guile@2.0            minisat              protobuf@2.6         solr@6.6
erlang@18            gv                   monax                pyexiv2              swig@3.04
ffmbc                hyper                node@6               rlvm                 tmux-cssh

==> Installing dependencies for jq: oniguruma
==> Installing jq dependency: oniguruma
==> Downloading https://homebrew.bintray.com/bottles/oniguruma-6.9.3.mojave.bottle.tar.gz
==> Downloading from https://akamai.bintray.com/f0/f00f8c6f8afd8875fed685a9190cb0c5e9b5ceef58ef1e489fb17a42bddc9672?__gda__=exp=1
######################################################################## 100.0%
==> Pouring oniguruma-6.9.3.mojave.bottle.tar.gz
🍺  /usr/local/Cellar/oniguruma/6.9.3: 17 files, 1.3MB
==> Installing jq
==> Downloading https://homebrew.bintray.com/bottles/jq-1.6.mojave.bottle.1.tar.gz
==> Downloading from https://akamai.bintray.com/71/71f0e76c5b22e5088426c971d5e795fe67abee7af6c2c4ae0cf4c0eb98ed21ff?__gda__=exp=1
######################################################################## 100.0%
==> Pouring jq-1.6.mojave.bottle.1.tar.gz
🍺  /usr/local/Cellar/jq/1.6: 18 files, 1MB
==> `brew cleanup` has not been run in 30 days, running now...
Removing: /Users/yuki/Library/Caches/Homebrew/git--2.19.2.mojave.bottle.tar.gz... (15.2MB)
Removing: /Users/yuki/Library/Caches/Homebrew/go--1.11.1.sierra.bottle.tar.gz... (140.5MB)
Removing: /Users/yuki/Library/Caches/Homebrew/icu4c--62.1.sierra.bottle.tar.gz... (25.4MB)
Removing: /Users/yuki/Library/Caches/Homebrew/jenv--0.4.4.tar.gz... (18KB)
Removing: /Users/yuki/Library/Caches/Homebrew/nkf--2.1.4.sierra.bottle.tar.gz... (157.8KB)
Removing: /Users/yuki/Library/Caches/Homebrew/node--11.2.0.sierra.bottle.tar.gz... (13.3MB)
Removing: /Users/yuki/Library/Caches/Homebrew/pcre2--10.32.mojave.bottle.tar.gz... (1.8MB)
Removing: /Users/yuki/Library/Caches/Homebrew/tree--1.8.0.sierra.bottle.tar.gz... (50.5KB)
Removing: /Users/yuki/Library/Caches/Homebrew/yarn--1.12.3.tar.gz... (1.1MB)
Removing: /Users/yuki/Library/Caches/Homebrew/z3-4.7.1.sierra.bottle.tar.gz... (27.3MB)
Removing: /Users/yuki/Library/Caches/Homebrew/openssl-1.0.2o_2.sierra.bottle.tar.gz... (3.7MB)
Removing: /Users/yuki/Library/Caches/Homebrew/gdbm-1.14.1_1.sierra.bottle.tar.gz... (182.5KB)
Removing: /Users/yuki/Library/Caches/Homebrew/gettext-0.19.8.1.sierra.bottle.tar.gz... (7.8MB)
Removing: /Users/yuki/Library/Caches/Homebrew/readline-7.0.3_1.sierra.bottle.tar.gz... (497.3KB)
Removing: /Users/yuki/Library/Caches/Homebrew/python@2-2.7.15.sierra.bottle.tar.gz... (18.3MB)
Removing: /Users/yuki/Library/Caches/Homebrew/wget-1.19.5.sierra.bottle.tar.gz... (1.3MB)
Removing: /Users/yuki/Library/Caches/Homebrew/cmake-3.11.3.sierra.bottle.tar.gz... (11.8MB)
Removing: /Users/yuki/Library/Caches/Homebrew/boost-1.67.0_1.sierra.bottle.tar.gz... (86.6MB)
Removing: /Users/yuki/Library/Caches/Homebrew/linkage.db... (48KB)
Removing: /Users/yuki/Library/Caches/Homebrew/sqlite-3.24.0.sierra.bottle.tar.gz... (1.7MB)
Removing: /Users/yuki/Library/Caches/Homebrew/nodebrew-1.0.0.tar.gz... (26.3KB)
Removing: /Users/yuki/Library/Caches/Homebrew/solidity-0.4.24.tar.gz... (1.1MB)
Removing: /Users/yuki/Library/Caches/Homebrew/portable-ruby-2.3.3_2.leopard_64.bottle.tar.gz... (12.4MB)
Removing: /Users/yuki/Library/Caches/Homebrew/ethereum-1.8.10.sierra.bottle.tar.gz... (64.2MB)
Removing: /Users/yuki/Library/Caches/Homebrew/portable-ruby-2.3.7.leopard_64.bottle.tar.gz... (12.4MB)
Removing: /Users/yuki/Library/Caches/Homebrew/go-1.10.3.sierra.bottle.tar.gz... (102.6MB)
Removing: /Users/yuki/Library/Caches/Homebrew/openssl-1.0.2o_1.sierra.bottle.tar.gz... (3.7MB)
Removing: /Users/yuki/Library/Caches/Homebrew/watch-3.3.15.sierra.bottle.tar.gz... (30.9KB)
Removing: /Users/yuki/Library/Caches/Homebrew/nmap-7.70.sierra.bottle.tar.gz... (7.1MB)
Removing: /Users/yuki/Library/Caches/Homebrew/libunistring-0.9.10.sierra.bottle.tar.gz... (1.4MB)
Removing: /Users/yuki/Library/Caches/Homebrew/ccache-3.4.2.sierra.bottle.tar.gz... (89.8KB)
Removing: /Users/yuki/Library/Caches/Homebrew/libidn2-2.0.5.sierra.bottle.tar.gz... (217.4KB)
Removing: /Users/yuki/Library/Caches/Homebrew/Cask/adoptopenjdk--11.0.1,13.tar.gz... (181.1MB)
Removing: /Users/yuki/Library/Logs/Homebrew/z3... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/tree... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/wget... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/libidn2... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/ccache... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/python@2... (3 files, 123.9KB)
Removing: /Users/yuki/Library/Logs/Homebrew/go... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/gdbm... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/cmake... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/boost... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/libunistring... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/icu4c... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/nkf... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/readline... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/sqlite... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/yarn... (100B)
Removing: /Users/yuki/Library/Logs/Homebrew/solidity... (6 files, 563.5KB)
Removing: /Users/yuki/Library/Logs/Homebrew/nodebrew... (104B)
Removing: /Users/yuki/Library/Logs/Homebrew/gettext... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/watch... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/node... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/jenv... (100B)
Removing: /Users/yuki/Library/Logs/Homebrew/nmap... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/openssl... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/ethereum... (64B)
Pruned 0 symbolic links and 2 directories from /usr/local


サンプル的に試してみる

例えば、以下の郵便番号による住所検索 API

curl で実行して、それを jq コマンドにパイプで渡してみます(queryパラメータの postal に指定している値が郵便番号)

curl "http://geoapi.heartrails.com/api/json?method=searchByPostal&postal=1060032" | jq


実行してみると以下のようになります。

$ curl "http://geoapi.heartrails.com/api/json?method=searchByPostal&postal=1060032" | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1882    0  1882    0     0  59882      0 --:--:-- --:--:-- --:--:-- 60709
{
  "response": {
    "location": [
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木一丁目",
        "town_kana": "ろっぽんぎ1ちょうめ",
        "x": "139.740991",
        "y": "35.665082",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木二丁目",
        "town_kana": "ろっぽんぎ2ちょうめ",
        "x": "139.737087",
        "y": "35.666974",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木三丁目",
        "town_kana": "ろっぽんぎ3ちょうめ",
        "x": "139.735452",
        "y": "35.663977",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木四丁目",
        "town_kana": "ろっぽんぎ4ちょうめ",
        "x": "139.733837",
        "y": "35.665489",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木五丁目",
        "town_kana": "ろっぽんぎ5ちょうめ",
        "x": "139.735248",
        "y": "35.658358",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木六丁目",
        "town_kana": "ろっぽんぎ6ちょうめ",
        "x": "139.729932",
        "y": "35.659856",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木七丁目",
        "town_kana": "ろっぽんぎ7ちょうめ",
        "x": "139.726863",
        "y": "35.664751",
        "prefecture": "東京都",
        "postal": "1060032"
      }
    ]
  }
}

これだけで、JSONを見やすく整形した状態で出力してくれます


jq コマンドは、かなり色々とできるはずなので、使い方調べながらもっと使いこなせるようになりたいです。