覚えたら書く

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

Go言語 - WindowsのアクティブなセッションのIDを取得する

Windowsではセッションという概念が存在しています。(セッション 0 の分離 - Windows 7 対応アプリケーションの互換性

複数セッションの中で現在アクティブになっている(物理コンソールにアタッチしている)セッションのセッションIDをGo言語で取得するサンプルです。


■サンプルコード

package main

import (
    "fmt"
    "log"
    "syscall"
)

var (
    kernel32, _ = syscall.LoadLibrary("kernel32.dll")

    wtsGetActiveConsoleSessionID, _ = syscall.GetProcAddress(kernel32, "WTSGetActiveConsoleSessionId")
)

func abort(funcname string, err error) {
    panic(fmt.Sprintf("%s failed: %v", funcname, err))
}

// ActiveSessionID : アクティブなセッションのIDを返す。アクティブなセッションが無い場合は-1を返す
func ActiveSessionID() int32 {
    r0, _, callErr := syscall.Syscall(uintptr(wtsGetActiveConsoleSessionID),
        2,
        0,
        0,
        0)
    if callErr != 0 {
        abort("Call WTSGetActiveConsoleSessionId", callErr)
    }
    return int32(r0)
}

func main() {
    log.Println("App start.")

    log.Printf("Active Session ID %d", ActiveSessionID())

    log.Println("App End.")
}

単純に、WindowsAPIのWTSGetActiveConsoleSessionIdを呼び出しているだけです。(WTSGetActiveConsoleSessionId function | Microsoft Docs


■実行結果

2017/05/18 05:28:32 App start.
2017/05/18 05:28:32 Active Session ID 1
2017/05/18 05:28:32 App End.



関連エントリ

コード品質特性を悪化させるには

プログラムのより良い設計を支える中心的概念としてコード品質特性があります。これに点数付けした場合に、悪い点をとるためにはどうすればいいかについて書きました。

以下内容のベースはJavaです


そもそもコード品質特性とは

ここでは、以下をコード品質特性としてとりあげます

  • 凝集度(cohesion)
  • 疎結合(loose coupling)
  • 重複無し(zero duplication)
  • カプセル化(encapsulation)
  • テスト容易性(testability)
  • 可読性(readablity)

各々の項目で悪い点を取るためにはどうすればいいか以下の各項目を参考にしてください


凝集度(cohesion)

基本的に、凝集度は高い方が良いと言われます。凝集度を低くするためには、クラスやパッケージの責務を増やせばよいです。そのためには以下に従うと良いでしょう。

  • 1クラスを大きくする
    • 1クラスが大きければ、基本的にはそのクラスが持つ責務は増えます
  • 大量のインターフェースを実装させたクラスを作る
    • 実装したインターフェースが多いということは、それだけ役割があるということです。したがって責務も多くなります
  • クラス名を極端に汎用的にする
    • 汎用的過ぎると色々なメソッドが配置できます。結果的に責務が増えます
  • パッと見たときのクラス名の意味が分からないようにする
    • クラス名の意味がよく分からないと、クラスが整理されることは無く、機能追加時に何となくメソッドが増えたりします
  • 1パッケージに大量のクラスを配置する
    • パッケージで見たときの責務がたくさんある状態と言えるでしょう


疎結合(loose coupling)

基本的に、結合度が低い(疎結合である)ことが望ましいと言われます。結合度を高くするにはどうすればいいかは以下項目を参考にしてください

  • 実装の継承をする
    • 実装の継承は親クラスと子クラスの結合度を高めてくれる道具の一つです
  • インターフェースを作らず実装クラスだけ作る
    • 実装クラス同士の依存関係は結合度を高めてくれます
  • DIは用いない
    • Dependency Injection(DI)を使ってしまうと結合度が下がることがほとんどです。基本的に使わないようにしましょう。
  • シングルトンパターンを多用する
    • シングルトンパターンは具体的なオブジェクトを必要とします。結合度を高くするには良い手段です。
  • Observerパターンを用いない
    • Observerパターンはクラス間の関係性を緩くすることがあります。Observerを介さずに、直接必要な機能を呼び出しましょう。
  • パッケージ間で相互依存をする
    • 一方向に比べて双方向の依存は依存関係をとても複雑にします。
  • 必要な機能を提供するクラスのインスタンスを生成するためにコンストラクタを呼び出す
    • new によりインスタンス生成するのは普通にやることですが、コンストラクタを呼び出すと必ず具象クラスが登場することになります。コンストラクタ呼び出しによりクラス間の結合度は高まります。


重複無し(zero duplication)

いわゆるDRY原則に反することをすればおのずと悪い点数となるでしょう。

  • コピペを繰り返す
    • コピペを繰り返せば重複が大量に発生します
  • 汎用的な処理を見出さない
    • ほとんどの業務アプリケーションにおいて、ビジネスロジックと呼べる核となる部分は大きくはありません。核となる部分以外は基本的に汎用的な物として切り出せます。が、あえてそれをしなければ各機能の中に重複した処理がたくさん生まれます。
  • ライブラリを使用しない
    • 世の中にある汎用的なライブラリを使用しなければ、基本的に車輪の再発明をし続けることになります。重複コードがきっと増えます

カプセル化(encapsulation)

スコープを広げればカプセル化は破られるでしょう。また、できるだけ内部の実装に依存されるようにすればさらに悪い点が取れるでしょう。

  • スコープを大きく広げる
    • どんなクラスもpublicにしましょう。メソッドやメンバのスコープも広げましょう
  • 実装の継承をする
    • 実装の継承をすると基本的にカプセル化が破られます。
  • ポリモーフィズムを用いない
    • ポリモーフィズムを使用することは、実装を隠蔽(カプセル化)することにつながります。


テスト容易性(testability)

ユニットテストをするためのセットアップ等を困難にすれば悪い点数となるでしょう。
そもそも意識的にユニットテストがし易いように作らなければ良い点は取りにくいです。

  • 外部環境に依存させる
    • 外部環境(ファイルやミドルなど)に依存したクラスを作ると、インスタンス化が困難になります。インスタンス化できなければ簡単にはユニットテストは書けません。
  • クラス間にインターフェースを介在させない
    • インターフェースを介在させるとテストし易くなります。できるだけ実装クラス同士で関係性を持つようにしましょう
  • Injectionを使用しない
    • コンストラクタInjection等で対象クラスに必要な機能を外からセットできるようにするとテストし易くなってしまいます。必要な機能は対象クラスの中で自ら取得するようにしましょう
  • メソッドの戻り値をできるだけvoidにする
    • 戻り値が無ければ対象メソッドの振る舞いが予定通りなのかどうか判定しにくくなります
  • クラスにたくさんのインスタンス変数を持たせる
    • 対象クラスのセットアップがかなり大変な状態になり、テストがしにくくなります。


可読性(readablity)

長いコードや深いコードを書きましょう。また命名を適当にやっていればソースコードは読みにくくなります。

  • 「ソースコードを汚くするには」に記載した内容を実施する
  • 基本的に以下のようなコードは可読性が低いと思えばよいです
    • 長い
    • ネストが深い
    • 条件分岐が多い
    • 名前が分かりにくい
  • マジックナンバーを使用する
    • マジックナンバーを使用するとその値の意図が伝わらなくなります。
  • 手続き的な書き方をする
    • 何がしたいか?ではなく、どうやって実現するのか?をソースコードに書き表せば可読性は下がるでしょう。


補足

コード品質特性を悪化させる方法をいくつか書きましたが、ほとんどの人が、通常はこのようなことを意識的にやることはありません
しかし、当初品質が高く保たれていたプログラムでも、機能追加をしてリリースを繰り返しているといつの間にか品質特性が悪化しています。
(機能追加によりプログラム改修をする際には、)我々は無意識のうちに品質の悪化につながるようなプログラムの書き方をしていることになります。
コードの品質を保つことについては、プログラムの新規作成時よりも機能追加する際により注意が必要ということになります。 プログラム改修時にメソッドの処理内に新しくifの条件分岐を足す場合も、本当にその分岐を配置していいのか?と自問すべきでしょう。



関連エントリ

複雑なプログラムに現れる傾向や特徴

「ソースコードを汚くするには」に記載したようなコードを書いていると、結果的に複雑なプログラムになってしまいます。
ここでは複雑なプログラムに現れやすい傾向や特徴などについて記載しました

基本的にJavaをターゲットにして本エントリを記載しています


クラス定義

  • import文に現れるパッケージが多種多様になっている
    • 多様なクラスとの依存関係ができて複雑な状態になっている
  • import文が異様に長い(importしているクラスが多い)
    • import文が長いということは依存しているクラスが多いということ。結果的に複雑になっている
  • クラスが大きい
    • 責務が多すぎるためにクラスが巨大化している
  • クラス名が長い
    • 複数の責務を負わされたクラスはその名称が長くなる場合がある
  • 定義されたpublicメソッドが多い
  • インスタンス変数が多い
  • クラス名とクラス内に定義されたメソッドが意味的に紐づかない
    • もともとの責務の範囲を超えたメソッドが定義されてしまった可能性がある
  • abstractクラスなのにでかい(重い実装を持っている)
  • メソッド名が長い
    • メソッド名に端的な名前を付けられないのは、他のメソッドとの区別をするために説明的な内容を記述しなければならないという状況を意味する場合がある。クラスの整理ができていないことに起因する。
  • コンストラクタが多い
    • 色んなタイプのインスタンスを生成するために多様なコンストラクタを定義しなければならなくなっている
  • staticメソッドだけを持つクラスが多い
    • staticメソッドだけを持つstaticなクラスに可変性を入れ込むのはかなり厳しい。そのため機能追加時や変更時などに柔軟な対応がしにくくなる


パッケージ

  • パッケージ間の相互依存が発生している
    • パッケージごとの役割の整理がうまくできていない場合にこのようなことが起きる
    • 一方向の依存に比べて、相互依存は異常に複雑な状態を生み出す


nullの扱い

  • メソッドの戻り値などに対する頻繁なnullチェックが行われている


文字列の扱い

  • 各処理の中で文字列をtrimしている
    • 頻繁なnullチェックなどと同じく渡された値が自分の欲している形になっていない可能性があるので、余計な処理をしなければならなくなっている
    • 基本的にどこか一カ所でやるべき


例外の扱い

  • 自分のLayerと無関係な例外をスローしている
  • 下位メソッドからの例外をjava.lang.Exceptionでcatchしている
    • 下位メソッドが複数の例外をスローしてきたり、実装に寄った例外をスローしてきたりした場合などに、例外の扱いが非常に煩わしくなり、java.lang.Exceptionで例外をまとめてcatchしてしまう状況をつくってしまう。


継承

  • 継承元の親クラスをどんどん遡っていかないとソースコードの内容を理解できない
    • 継承階層が深くなりすぎている。継承は使いどころを間違えると諸刃の剣となる


命名

  • メソッドが機能追加によってそのコード量が増加した場合でもメソッド名が変わっていない
    • 例えば機能追加によりコード量が当初よりも2倍,3倍に増えた場合に、普通に考えれば同じ名前(意味)で保ち続けられるはずはない。適切にメソッド名を変えるか、メソッドを上手く分割する等の対応が必要なはず。


if文

  • ifの条件文が何を意味しているのか一見して理解できない
    • "何の判断をしているのか?"ではなく"どうやって判断するのか?"という条件判定の実現方式になっているとソースコードを読む側は理解できない


ユニットテスト

  • ユニットテスト時にテスト対象クラスのインスタンス化ができない
  • ユニットテスト時に、実行対象のメソッドに渡すパラメータがインスタンス化できない
  • ユニットテストしようとした際に、テスト対象のクラスと直接関係のない外部環境(DBやファイルやネットワーク環境など)のセットアップが必要になる



関連エントリ

「APIデザインの極意」-優れたAPIを決定づけるもの

「APIデザインの極意」第3章 優れたAPIを決定づけるもの で気になった部分の抜粋

APIデザインの極意 Java/NetBeansアーキテクト探究ノート

APIデザインの極意 Java/NetBeansアーキテクト探究ノート


メソッドとフィールドのシグニチャ

  • システムのコンポーネント間でのコミュニケーションにリフレクションが必要なのであれば、何かが間違っているか、既存のAPIでは不十分であることを示している


APIとしてのテキストメッセージ

  • テキストメッセージを解析するしかない状況に気を付ける
    • 他の方法で情報が利用できなければ、人々はコードが生成したテキスト出力を解析してしまう


プロトコル

  • ネットワークを介して送信されるメッセージの形式を意味するプロトコルもAPIの一つである
    • このAPIの利点は、「あなたの」サーバで起きているすべてのアクセス頻度と通信を計測して分析できること
  • ネットワークプロトコルの問題点は、アプリケーションとの会話に使用される様々なクライアントやプロトコルバージョンが拡散していくこと
  • プロトコルのようなAPIが後方互換性を保ちながら発展する必要がある理由は、個々の参加者の独立したライフサイクルによって問題が拡大するからである
  • お互いに通信しあう2つのプログラムは異なるバージョンであり、少し異なったバージョンのプロトコルを必要とするのはほぼ確実である。それでもプログラムは通信する必要がある


振る舞い

  • APIが提供している抽象的概念がどれだけ優れているかに関係なく、 その背後にある実装はたいていは漏れ出しており、その結果APIの一部にもなっている
  • コンポーネントの振る舞いが変わらない場合にだけ、そのユーザは最終アプリケーションでそのコンポーネントを何も考えることなく新たなバージョンに置き換えることができる


APIの広い定義

  • APIの定義は、クラスやメソッドあるいは関数とシグニチャの単純な集まりをはるかに超えている
  • 大きなシステムコンポーネント無知な状態で組み立てるのに役立つという意味でのAPIは単純なテキストメッセージから複雑で把握するのが難しいコンポーネントの振る舞いなどと範囲が広い


APIの品質検査方法

  • APIの美しさだけが、優れたAPIの唯一の測定方法になることはありません
  • 使いやすく、広く受け入れられ、生産的なAPIを設計するという目的からそれるべきではない
  • 私たちは、個々のAPIが特定の目的を満足させているかを測定する方法を作りだす必要がある


■理解しやすさ

  • APIの作成に最も似た活動は本を書くことです
    • 一人の著者と多数の読者がいる。読者は著者について何かしら知っているが、著者は読者についてはほとんど知らない
    • 読者のスキルと知識を正しく推測することは、理解しやすいAPIにするために細心の注意が必要な芸術的な部分となる
  • ほとんどのAPIのユーザは、 自分が行いたいことと似たようなことを行っている既存アプリケーションを探し、そのアプリケーションをコピーして必要に応じて修正する という方法をとる
  • APIの使用方法の多くの例を示すことで開発者が必要とすることに近い例を見つける可能性が高まる
    • 開発者がAPIを理解する可能性を大幅に高める
    • APIが使用している概念が新しければ新しいほど豊富な例が役立つ


■一貫性

  • APIのユーザがある概念を理解するのに時間を費やさなければならないのであれば、その概念をAPI全体に対し一貫して適用できることが重要


■発見できること

  • どんなに美しいAPIであっても、 想定している利用者がそのAPIを発見できなかったり、使用方法を容易に理解できなければ役に立たない
  • APIのユーザはクラスの集まりには興味はない
    • ユーザは自分の仕事を終わらせることに興味があり、そのためにはAPI利用例を見て行いたいことに近い方法を選択できるようにすることが重要である
  • 提供するAPIの種類に関係なく開始地点としての役割を果たし、人々の問題を解決するために正しい方向に向かわせる単一の場所を提供することは重要
    • 人々はクラスという観点から考えたりはしないので、その開始地点を実際の目的や仕事あるいは少なくとも期待される目標や仕事に基づくように最適な方法で構成することが重要である


■単純な仕事は簡単でなければならない

  • APIの基本的な誤りは、1つのAPIに異なるグループが興味を持つ複数の項目を入れてしまうことである
    • APIの1つの側面にしか興味が無い人々には、全く異なるグループに向けて設計された部分によって注意がそらされてしまい、興味のある部分を発見できなくなってしまう
    • 取るべき手段は、 APIを2つ以上の別の部分に分けてしまうこと
      • 呼び出し側向けとAPIに機能を追加するプロバイダ向けでパッケージ(名前空間)を分けてしまう等
  • たとえばJavaMail APIは膨大な数の概念とクラスが混在しているので、メールの送受信をするだけでもかなりの分量のコードを書く必要がある。
    • このAPIは、プロバイダ向けに最適化されてしまっており、この誤った最適化によりJavaのメールアプリケーションを比較的少なくしてしまっている原因になっている


■投資の保全

  • APIのユーザがそのAPIで本当に満足するためには、新しいバージョンのライブラリのリリース時に、それまで作成したものが動かなくなることが無いことを信用できなければならない
  • ライブラリに対してコーディングすることは時間・学習・労力・お金の投資である
    • API設計者のもっとも重要な責任は、そのライブラリを使用する人々の投資が無駄にならないようにすること
  • ユーザがAPIで良い経験を積めば積むほどAPIの良さを周りに話し、そのAPIを使用することに良い印象を持ち、そのAPIの利用は促進されて広く知られるようになる
    • 結果としてAPIに好意を持って利用している人たちのコミュニティが形成される
    • そのため、参加者の投資を無駄にしないことが重要であり、API契約を互換性のある方法、あるいは、少なくとも予測可能な方法で発展させることに努めることが常に重要



関連エントリ

「APIデザインの極意」-APIを作成する動機

「APIデザインの極意」第2章 APIを作成する動機 で気になった部分の抜粋

APIデザインの極意 Java/NetBeansアーキテクト探究ノート

APIデザインの極意 Java/NetBeansアーキテクト探究ノート


分散開発

  • アプリ全体を組み立てる際には個々のビルディングブロックを糊付けする必要があり、それらのブロックはお互いに会話をする必要がある。
  • たいていは、明確に定義されたAPIを通して話しをする。
  • 各コンポーネントのAPIは、みんなの無知( ※選択的無知)を増加させるための最初の一歩である。
  • APIは機能を提供しているコンポーネントの詳細を理解する必要性を最小限にしてくれる。
  • APIは各コンポーネントの機能と内部実装に関する抽象概念とみなすことができる
  • 無知な利用が成功するためには、 APIがライブラリの本質的な真意をきちんと反映していることが重要

(※選択的無知 ・・・ 何を深く知り、何を深く知る必要がないかを積極的に選択すること)


アプリケーションのモジュール化

  • コンポーネントには名前が必要
  • 名前は一意でシステム内でコンポーネントを特定できて、そして、 説明的でなければならない
  • コンポーネントが名前を持つ必要があると期待することは自然な流れに思える。しかし、この点を注意深く見てみると、 これらの名前はコンピュータよりも人にとって重要であると分かる。
  • コンポーネントがたいていは人が理解できる名前を持っているという事実は、名前が主に対象としているのは人間という証です。
  • モジュール方式のシステム内の個々のコンポーネントは必要とするほかのすべてのコンポーネントに関する情報を保持ししている。
  • モジュール方式のシステムではほかのコンポーネントへの依存は、 依存先コンポーネントの識別名と必要な最低バージョンを示すことで表現することができる。


すべては、コミュニケーション次第

  • アプリケーションは無知の概念に基づいており、アプリケーションの最終組み立てを行う人を限界まで無知に近づけようとする。
  • 結果として理想的なアプリケーションは、モジュール方式のアーキテクチャに基づいている。
  • APIがうまく設計されていなければ、使用する側はそのAPIの作者の意図を正しく理解できずに誤用する。
  • APIが信頼できるものであるためには、 誤った利用を防ぐ必要があり 、また、 発展する準備ができていなけれなばらない。
  • 最初に作成された時に完璧であるAPIはほとんどありません。
  • APIの将来的な改善をおこなうための重要な条件は 分離です。


経験的プログラミング

  • APIは自己文書化されている必要がある。
  • 言い換えると ドキュメント無しで利用可能である 必要があります
  • APIは解決すべき課題へとAPIのユーザを自然に導かなければならない



関連エントリ