ゆるっとIT vol.12「3年ぶりに帰ってきたIT怪談」 カンペ

昨日2022/10/27に行われた『ゆるっとIT vol.12「3年ぶりに帰ってきたIT怪談」』のカンペです。
これを見ながら話してました。

前振り

今日はほんとに怖い話をするので、資料なしでやりますね。

昔話

ちょっと昔話をしましょう
ぼくの最初の仕事はPHPでした。PHP3とPostgreSQLで英語学習サイトをつくるというものです。
当時webでプログラムを動かすというのは出たばかりで、請け負う業者がいなかった
みんなVBやってましたからね
なので大学追い出されたてで何の実績もないぼくに仕事がまわってきた。

もうちょっとして、東京の会社に声をかけられました。
で、iPhoneアプリをつくってほしいと。
これも、iphone出たばっかりで技術者がいなかった。
なので福岡のぼくに話がまわってきたわけですね。

共通するのは、新しい技術で技術者がいないので実績ないけど仕事がまわってきたことです。
若い人は年寄りがやってない新しい技術をやるって感じでしたね。

現在の話

現在の話をしましょう
iphone 14がでましたね。でもうれてません。売れてないので減産しました。
iPhoneというのは、一番最新で一番高性能なCPUがのるデバイスです。
それが売れてない。
もう速いCPU作っても売れない。ということになってます。
そうすると速いCPUがつくられなくなる。

これはハードウェアの話ですけど、ハードウェアがとまるとソフトウェアもとまります。
もうすでにとまりつつありますね。あたらしいプログラミング技術がでてない。もちろん細かいものは出てますが、ちょっとした便利機能です。
みなさんに会うの3年ぶりくらいですけど、3年前と同じ言語やフレームワーク使ってる人おおいんじゃないですか
そして、気になる技術もないということないですか
rustやpythonに興味あっても当面仕事ではつかわない感じですよね。

未来の話

それから、未来の話をしますね
プログラミング教育が義務化されました。
「プログラミングネイティブ」がどんどんでてくる
いまでも学生でプログラムコンテストやってましたーという人がたくさんいる
やつら、プログラム超くめる

怖い話

じゃあ怖い話をします。
技術がかわらなくなって、出来のよい新人がはいってくるとどうなるか
いままでは若い人は新しい技術をひっさげてきたわけです。
住み分けがあったわけですね。
cobolerのように、若者よせつけないレガシーのお守りで生きてはいけばいい
もしくは道具がかわるなら新しい道具のつかいかたおぼえればいい

でも同じ技術を高度に使う能力をひっさげてくるわけです。
技術かわらないんで勉強しなくても生きていけるんだけど、そうすると若者に圧倒的な差で窓際においやられますよ、という怖い話でした。
若い人にとっては、上がつまってるのでなかなかアピールできないということでもあります。

基礎をやりましょう。アルゴリズムやら論理、あと最近では倫理という技術も大切になってきてます。
経験をつみましょう。ちゃんと考えながら経験しておけば、能力差をまかなえます。

そうやって、怖い世界にたちむかっていきましょう

Javaの-XmsはMemory Startingの略?

Javaのメモリ確保指定のオプションで、最大メモリは-Xmx、初期メモリは-Xmsで指定します。
-XmxのほうはMemory maXimumかなーと感じるのだけど、-Xmsのほうはminimumでもないしなんだろーと思いながら「まぁ気にしてもしかたない」と25年くらい放置してたわけです。
それがちょっとTwitterで話題になってて、調べてみたらStack Overflowにそれっぽいものがありました。

Yes, ms = minimum heap size / heap memory start size and mx = maximum heap size.

https://stackoverflow.com/questions/58164083/why-did-java-chose-xmx-and-xms-naming-convention-for-heap-sizes

-Xmsは最小メモリではなく初期メモリなのでMemory Startingというのは説得力があります。

enumの使い方のおさらいと高機能enumとしてのSealedクラス

Javaでは複数の定数をまとめて扱う型としてenum(列挙型)が用意されています。
これはこれで便利なのですが物足りないところもあって、それがSealedクラスなどを使うことで解決できるようになるので、解説します。

enum

enumは状態やデータ区分を表すのによく使われます。
構文は次のようになります。

enum 型名 {
  列挙1, 列挙2, ...
}

例えば次のような状態を表すとします。

この状態を表すenumは次のようになります。それぞれの値は大文字で書くようにします。

enum State {
  READY, RUNNING, SUSPENDED, TERMINATED
}

enumではそれぞれの値ごとに処理を行うということがよくあります。そこでswitchと相性がいいです。

State s = State.READY;

switch (s) {
  case READY -> System.out.println("準備完了");
  case RUNNING -> System.out.println("実行中");
  case SUSPENDED-> System.out.println("停止中");
  case TERMINATED-> System.out.println("終了");
}

switch文の場合はenum値が足りなくても大丈夫ですが、switch式では全てのenum値を処理しないとエラーになります。

このことを考えると、enum値すべての処理をするswitchは式にしておいたほうがいいですね。(Java 14以降)

var message = switch (current) {
    case READY -> "準備完了";
    case RUNNING -> "実行中";
    case SUSPENDED -> "停止中";
    case TERMINATED -> "終了";
};

enumに値を持たせる

enum値それぞれに数値などを割り当てたい場合があります。そういう場合はフィールドとして定義することができます。 メソッドも定義できるのでgetterを用意するか、toStringメソッドをオーバーライドすることになりますね。 フィールドはprivate finalにしておきましょう。

enum State {
    READY("準備完了"),
    RUNNING("実行中"),
    SUSPENDED("停止中"),
    TERMINATED("終了");

    private final String name;
    State(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    @Override
    public String toString() {
        return name;
    }
}

ただまあ、フィールドとコンストラクタとgetterを定義していくのめんどいので、Lombokを使いたくなります。

@Value
enum State {
    READY(1, "準備完了"),
    RUNNING(2, "実行中"),
    SUSPENDED(3, "停止中"),
    TERMINATED(4, "終了");

    int code;
    String name;

    @Override
    public String toString() {
        return name;
    }
}

Sealedクラスを拡張enumとして使う

JavaenumはCのenumよりは便利なのですが、パラメータを追加したいという使い方ができません。
例えばMouseEventというenumがあって、CLICKEDとMOVEDがあるんだけど座標も持たせたい、というような場合です。あとCLICKEDにはどのボタンかも持たせたい。

という場合にSealedクラスが使えます。実際に使うのはinterfaceになるけど。

enum Button {
    LEFT, MIDDLE, RIGHT
}
sealed interface Event {
    record Clicked(Button button, int x, int y) implements Event {}
    record Moved(int x, int y) implements Event {}
}

sealedをつけたクラスやインタフェースでは、そのnestedクラスだけで継承が行えるようになります。nestedクラス以外で継承を行う場合にはpermitsで指定します。

sealed interface Event permits Clicked, Moved {}
record Clicked(Button button, int x, int y) implements Event {}
record Moved(int x, int y) implements Event {}

そして、Java 19でプレビューとして導入されたpattern matching for switchとrecord patternを利用すれば、enumのように扱えます。

Event e = new Event.Clicked(Button.RIGHT, 7, 3);
var message = switch (e) {
    case Event.Clicked(var b, int x, int y) -> 
            "%sボタンが%d,%dでクリックされた".formatted(b, x, y);
    case Event.Moved(int x, int y) -> 
            "%d,%dに動いた".formatted(x, y);
};

ここでEvent型はSealedなので、継承するクラスはClickedかMovedに限られるため、可能なすべての型をチェックしたことになります。

ということで、recordとsealedクラスとパターンマッチとswitch式を組み合わせると高機能enumのように使えるという話でした。

インスタンスとオブジェクトの違い

インスタンスとオブジェクトは混同しがちで区別がようわからんになりがちです。
とりあえず某所で説明したものを再構成します。

※2022/12/10追記: クラスに対するのはインスタンスになるべき(たとえばクラス変数とインスタンス変数)なので、ちょっと修正するべきですが、このエントリはそのまま残してます。

クラス・インスタンス・オブジェクト

クラスをインスタンス化(実体化)したものがオブジェクト(物)です。

実際に在るものはクラスとオブジェクトで、インスタンスはそれらの関係です。colorsやsportsが並んでるツリーが「オブジェクト」で、右のパレットに並んでるTreeが「クラス」、Treeからみたときのツリーが「インスタンス」ということになります。

ここでツリーはオブジェクトでもインスタンスでもあります。
このように、同じものをオブジェクトともインスタンスともいうことができるので混同してしまうわけですが、インスタンスというときには視点がTreeクラスである必要があります。

オブジェクトは必ずクラス(or なにかの型)と紐づいています。しかし、インスタンスそれ自身はクラスと結びついていません。なので、「クラスのインスタンス」という言い方ができます。
一方で、「クラスのオブジェクト」というと違和感があります。ここでオブジェクトを「クラスのインスタンス」で置き換えると「クラスのクラスのインスタンス」となってクラスがかぶってしまうからです。そうすると、Rubyのようにクラスがオブジェクトとして扱える言語で、クラスをあらわすオブジェクトのことを「クラスのオブジェクト」といっている感じになります。

ただし、入門者にJavaの説明をするときには、インスタンスという言葉を使うだけで難しさがあがってしまうので、本来は「Treeクラスのインスタンス」というべきところを「Treeクラスのオブジェクト」と言うことがあります。

クラスはオブジェクトか?

Rubyのようにクラスがオブジェクトとして扱える言語」と書きましたが、Javaではクラスはオブジェクトではありません。

Javaではオブジェクトはクラスのインスタンスか配列かどちらかです。クラスは含まれません。

An object is a class instance or an array.

https://docs.oracle.com/javase/specs/jls/se18/html/jls-4.html#jls-4.3.1

ところで配列はオブジェクトではあるけどクラスのインスタンスではないわけですね。 なので「オブジェクトとインスタンスは同じ」という説明があったとき「いやいや、配列はオブジェクトだがインスタンスではないぞ」というツッコミができるわけです。
※追記 「instances of the same array type」のような記述が言語仕様にあったりするので、配列もインスタンスです。

Classクラスは?

java.lang.Classクラスあるじゃん、クラスもオブジェクトじゃん」と言いたくなるかもしれません。
Classクラスは、Javaのようにクラスがオブジェクトではない言語で実行時にクラスを扱うためのクラスということで、メタクラスという位置づけになってます。
「めたxx」は「xxのxx」と置き換えることができるので、メタクラスは「クラスのクラス」です。
(「メタモンはモンのモン」は既出です。モンスターのモンスターですね)

他の言語では?

上で書いたようにRubyではクラスはオブジェクトです。Smalltalkの影響が強い言語ではクラスがオブジェクトとして扱えるみたいなことがWikipediaに書いてありますね。
https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9

で、そうするとクラスのオブジェクトとそっからインスタンス化されたオブジェクトとようわからんになるので、クラス・オブジェクトとインスタンス・オブジェクトのような使い分けをするらしいです。

ちなみにJavaScriptでは

というように、クラスはオブジェクトか?いやオブジェクトじゃない、とかクラスもオブジェクトでインスタンスもオブジェクトで、とかいう話に
「ようわからんわ!クラスとかいらんのじゃ!ぜんぶオブジェクトじゃ!」
ってブチ切れて、オブジェクトだけで全部やろうとした人がいました(ブチ切れたかどうか知りませんがそういうややこしさの解消がキッカケだったようです)

つまりクラスの代わりになるような、元ネタになるオブジェクトを用意して、それをコピーして別のオブジェクトを生成という方針です。このとき元ネタになるオブジェクトをプロトタイプといいます。
このようにオブジェクトを生成する言語を「プロトタイプベースのオブジェクト指向」と呼びます。
代表的なのがJavaScriptですね。
まあ結局プロトタイプベースでは考え方は楽かもしれないけど使い方が超ややこしいということになって、classが導入されたわけです。
※追記 いずれにしろJavaScript(TypeScript)でクラスのようなものの出番はあまりないという指摘があった。

結論

どうやってもややこしい

ノンアル ヒューガルデン来た!

ベルギーで飲んだときにこれはうまいと思って日本でも飲みたいと思ってたノンアル ヒューガルデン、2年前に日本で買える話が流れて残念に思っていたのですが、ようやく買えるようになりました。

ヒューガルデンの味がする!コリアンダーの華やかさとオレンジピールのさわやかさをちゃんと感じます。少し香りの華やかさが控えめになって甘めになった感じ。
もちろんアルコールがない分の物足りなさはありますが、逆にいえばほんとにヒューガルデンからアルコールを抜いた感じです。

酵母が入っていて、最後に振って注いで酵母を入れると味がかわるのもヒューガルデンと同じです。甘さが気になる場合は、振らずに注いで飲んでみるといいかもしれません。もったいないので最後だけ酵母いれましょう。

ベルギーではビールを飲んだ帰りに1本ノンアルヒューガルデンを買って帰ってホテルで飲んでたのですが、そうすると普通のヒューガルデンでした。一度まちがえてアルコール入りヒューガルデンを買って飲んだとき、今日のはちょっと濃いなって思っただけだったくらい。

韓国製造のようで、ベルギーで飲んだものとはちょっと違う可能性はありますが、アルコール入りのヒューガルデンも韓国製造なので。

ほんもののヒューガルデンと同じく、日本のどの料理にも合うという感じではないので飲む場面は選ぶと思うけど、ヒューガルデンを常備してる人なら定期購入してよさそう。

他のノンアルビールはこちらでまとめてます。

オブジェクト指向は継承で多態するプログラミング

オブジェクト指向って継承による多態があるからこそなんだけど、継承が非推奨になって以降に雰囲気でオブジェクト指向を知った人には、継承はオプションでカプセル化だけでオブジェクト指向って言ってしまいがちに思います。 実際はカプセル化オブジェクト指向固有じゃなくて、クラスでカプセル化を実現してるだけです。

さまざまな人のオブジェクト指向の定義

本来ならどのように継承こそがオブジェクト指向なのかという説明をするんですが、かなり長くなりそうなので、とりあえずはいろいろな人たちのオブジェクト指向の定義を抜き出してみます。
「ここに挙がってるのはオブジェクト指向の一派にすぎない」というような意見もありますが他の派閥についてまとまって定義され共通認識になっているようなものは見当たらないので、プログラミングの指針には なりづらいと思います。

ストラウストラップ

C++を産んだストラウストラップは「C++の設計と進化」で次のように言ってます。ここでデータ抽象化というのはカプセル化だと考えて問題ないと思います。

オブジェクト指向プログラミングは継承を使うプログラミングだ。データ抽象化はユーザ定義タイプを使うプログラミングだ。ほとんど例外なく、オブジェクト指向プログラミングはデータ抽象化のスーパーセットだ

ランボー

オブジェクト指向方法論OMTを開発したランボーは次のように書いてます。ここでアイデンティティまで含めているのが面白いところです。値型との区別ですね。

オブジェクト指向的アプローチに要求される特徴が正確に何か、ということに関してはいくつかの議論があるが、それらの議論は普通以下の4つの面を含んでいる。すなわち、アイデンティティ、分類、多相性、そして継承である

ブーチ

ブーチ法のブーチは、次のように書いています。

もし言語が継承を直接サポートしないならば、それはオブジェクト指向とはいえない

Booch法:オブジェクト指向分析と設計 (Professional Computing Series 3)

Booch法:オブジェクト指向分析と設計 (Professional Computing Series 3)

  • 作者:Grady Booch
  • アジソン・ウェスレイ・パブリッシャーズ・
Amazon

メイヤー

オブジェクト指向入門」を書いたメイヤーは、次のようなオブジェクト指向の原則を書いています。一言で継承が重要とは書いていませんが、著書全体を通して継承の使い方を解説しています。

方法論と言語には、中心的な概念としてクラスの概念が必須である
すべての型はクラスに基づいていなければならない
ほかのクラスを継承するクラスを定義できなければならない

ヴァン・ロイ、ハリディ

ガウディ本として知られる「コンピュータプログラミングの概念・技法・モデル」には次のような記述があります。
ガウディ本はオブジェクト指向だけではなくプログラミングのさまざまなモデルや概念をまとめて紹介した本で、他の手法との対比をよりはっきりと行っています。

オブジェクト指向プログラミングとは、オブジェクト、データ抽象、明示的状態、多態性、および継承を使うプログラミングである」
「 他の計算モデルと比較すると、多態性と継承を使うことが主たる特徴である」

また、ガウディ本には次のような記述があります。高階プログラミング技法というのは関数型と考えるといいです。

オブジェクト指向言語ではできるだけ明示的状態と継承を使用することが推奨される。」「このことは一見、単純で直観的であるが、実際はプログラミングを複雑にする」「好ましいプログラミング技法を記述するための共通用語を定義するデザインパターンは普通、継承を使って説明される。多くの場合、より簡単な高階プログラミング技法で十分であろう」

次のように、特別な場合以外はオブジェクト指向のアプローチを避けたほうがいいということも書いています。

オブジェクト指向に頼るのは、プログラム構造が著しく簡単になる場合に限ることを奨める。プログラムに、密接に関連するデータ抽象の集合があって、明らかに継承の必要があるような場合がその一例である。そうでなければ、もっと簡単なプログラミング技法を使用することを奨める。」

2004年時点では まだオブジェクト指向の熱気が残っていたので、このくらいの控えめな記述になったのかなという感じがします。

リファクタリングはエンジニアの福利厚生であり管理指標への影響はほとんどないんでは

おそらくリファクタリング工数を確保する説得力のある材料がほしくて、リファクタリングの効果をどう示すか悩んでる人がいたのですが、リファクタリングって非開発者に示せるような数字だすのは難しいよねという結論になったので、そのまとめ。
工数としてはコード管理費みたいな感じで乗せるのがよさそう。

まず、リファクタリングはそれ自体では価値を示せません。人工衛星に搭載するプログラムで、動きだしたらメンテナンスできないようなコードを最後にリファクタリングしたとして、どのような価値を示せるかと考えると想像できるのではないかと思います。

なのでリファクタリングの価値というのは、その後で新しいコードを追加したり既存のコードを変更したりといった作業がどれだけ作業時間短く品質高くなったかという間接的な指標で測ることになります。

ここでまず、最初のコードを書いた人とリファクタリングする人が同じなら、そこまで保守性かわるか?という疑問があります。
リファクタリングで保守性高いコードを導き出せる人は最初からそれなりの保守性のコード書いてるのでは?保守性オワットルコードを書く人はリファクタリングしても保守性を盛り込めないんでは?と。
保守性オワットルコードを、保守性高いマンがリファクタリングするとき、それリファクタリングの範囲ですむのか、アーキテクチャ変更になるんでは、とか。

また、コードの追加変更って、まずどこをいじくるか探して、新しいコードを既存コードに接続すると、あとはずっと同じコードをいじくり続けがちになると思います。機能開発の間に開いてるファイルは決まっているというか。
そうすると、リファクタリングの効果が出るのはどこをいじくるか探すところで、機能開発が進むと効果は薄まっていくのではという気がします。

あと、「住めば都」原理により、人間はそれなりに散らかったコードでも割と適応して、結構コードを把握できてしまって、リファクタリングの効果が構造の変化ほどは出ないんではないかというのもあり。

そして、リファクタリング効果が高い条件設定をすると、それはそのままリファクタリングができない条件にもなりがちということもあります。たとえば、コードは保守性オワットルマンが書き、それを保守性高いマンがリファクタリングに集中して構成変更していく、と。それは効果ありそうなんですが、現実には保守性高いマンは機能開発にまわされて、リファクタリングというお金を産みにくい作業にまわしてもらえないように思います。

もちろん、リファクタリングがちゃんとされているコードと書き散らしのコードでは、作業の気持ちよさが違います。ということはリファクタリングはエンジニアの精神衛生を保つ福利厚生の面が高いんではないでしょうか。
福利厚生、つまり、エンジニアに与えられる金銭以外の報酬ってことです。

ということでリファクタリング工数とってまとめてやるんじゃなく、スキマ時間で気がついたときにやったほうがいいのではないかと思います。そしてそういったリファクタリング作業を「バグが入る可能性があるからやるな」などと言われないよう、テストをちゃんと書いてエンバグを防いで、リファクタリングできる環境を整えておきましょう、というのが現実的かなという結論でした。

合わせて読みたい
TDDで「テストばかり書いて間に合うのか?」と質問されたときの正解 - きしだのHatena