日々常々

ふつうのプログラマがあたりまえにしたいこと。

今年の何%が経過したかを求めよう

この手のコードはAIがシャシャッと書いてくれる時代。 あ、冒頭はAIのコード貼ってるけど、本題はそこじゃないです。序盤は適当に流してください。本文書こうと思ったら「それAIでよくね?」と脳内で囁かれたので、添えただけ。

いくつか生成

ChatGPTさんに雑に投げてみます。「今年の何%が経過したかを求めるコードをJavaで書いてください」と。

こちらが Java で今年が何%経過したかを求めるコードです!

// 今日の日付を取得
LocalDate today = LocalDate.now();

// 今年の最初の日と最後の日を取得
LocalDate startOfYear = LocalDate.of(today.getYear(), 1, 1);
LocalDate endOfYear = LocalDate.of(today.getYear(), 12, 31);

// 年の経過日数と年間の日数を計算
long daysPassed = ChronoUnit.DAYS.between(startOfYear, today);
long totalDays = ChronoUnit.DAYS.between(startOfYear, endOfYear) + 1;

// 経過率を計算(小数第2位まで表示)
double percentage = (daysPassed * 100.0) / totalDays;

// 出力
System.out.printf("今年の%.2f%%が経過しました。\n", percentage);

まぁ動く。

他のモデル

JetBrains AIではいくつか使えるので、GPT-4o、Claud 3.5 Sonnet、Gemini Pro 1.5に投げてみます。 モデルを変えると口調も変わるのは面白いです。

GPT-4oはGhatGPTとほぼ同じだったので省略。

Claud 3.5 Sonnet

Javaで今年の経過率を計算するコードを提供いたします。うるう年も考慮した、より正確なバージョンを作成しました:

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class YearProgress {

    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        int year = today.getYear();
        LocalDate start = LocalDate.of(year, 1, 1);
        LocalDate end = LocalDate.of(year, 12, 31);
        long totalDays = ChronoUnit.DAYS.between(start, end) + 1; // うるう年対応
        long daysPassed = ChronoUnit.DAYS.between(start, today) + 1;
        double percentage = (double) daysPassed / totalDays * 100;

        System.out.printf("今年の経過パーセンテージ: %.2f%%%n", percentage);
    }
}

その +1 ってうるう年対応なの……?

Gemini Pro 1.5

かしこまりました。Javaで今年の経過パーセンテージを計算するコードを、シンプルさと正確さを両立させて記述しました。

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;

public class YearProgress {
    public static void main(String[] args) {
        // 現在の日時を取得(ミリ秒まで)
        LocalDateTime now = LocalDateTime.now();
        
        // 今年の開始時点
        LocalDateTime startOfYear = LocalDateTime.of(now.getYear(), 1, 1, 0, 0, 0);
        
        // 今年の終了時点
        LocalDateTime endOfYear = LocalDateTime.of(now.getYear(), 12, 31, 23, 59, 59, 999999999);
        
        // 今年の総時間(ナノ秒)
        double totalNanos = ChronoUnit.NANOS.between(startOfYear, endOfYear);
        
        // 現在までの経過時間(ナノ秒)
        double elapsedNanos = ChronoUnit.NANOS.between(startOfYear, now);
        
        // パーセンテージを計算
        double percentage = (elapsedNanos / totalNanos) * 100;
        
        // 結果を表示(小数点第6位まで)
        System.out.printf("今年は %.6f%% 経過しました。%n", percentage);
        
        // より詳細な情報を表示
        System.out.println("現在時刻: " + now.format(java.time.format.DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH時mm分ss秒")));
        System.out.println("年初からの経過時間: " + String.format("%,d", elapsedNanos) + " ナノ秒");
        System.out.println("今年の総時間: " + String.format("%,d", (long)totalNanos) + " ナノ秒");
    }
}

ナノ秒ときた。 日数とは指定してないからまぁうんって感じではあるが、うん。

irof

自分をこの枠に入れるなと。 聞く前にjshellで書いてました。

(LocalDate.now().getDayOfYear() * 100) / LocalDate.now().with(TemporalAdjusters.lastDayOfYear()).getDayOfYear()

このブログ書こうとしたのがふと「今年何日経ったっけ?」で。

このポストをするために "%d/%d".formatted(LocalDate.now().getDayOfYear(), LocalDate.now().with(TemporalAdjusters.lastDayOfYear()).getDayOfYear()) を書いてから「これくらいのはもう人間が書くものじゃないんだよなぁ」と思いながら冒頭に行った感じです。

本文

生成AIの生成したものを貼って終わりにするのも一興なのかもだけど、見ながらコメント書いたら全然違うのになるのよね。過去のブログとか入力して「私の書きそうなこと書いて」ってやったらできるのかもしれないけど、趣味だから書かせてほしいです。そのうちこう言うの書いても「AIでいいじゃん」って言われそう。ままならないね。

TemporalAdjuster

TemporalAdjuster さんとか with メソッドとか。あまり見ないけど、知らないと使わないは違うから知って使えるようになっておくと何かと便利です。

とはいえ1月1日や12月31日のために TemporalAdjusters を使う必要はないと思います。 LocalDate.of(2025, 1, 1) の方が読みやすいし。でも LocalDate.of(today.getYear(), 1, 1) は読みやすいと思わない。これやるなら today.with(MonthDay.of(1, 1)) じゃない?まぁ MonthDay も存在知られてなかったりするからやらなくていいけど、引数の対称性が取れていないのはもにょります。コードレビューとかでも言わない程度の個人の範囲だけど。 MonthDay なしなら withMonth & withDayOfMonth とかある。

並べてみる。

LocalDate.parse("2025-01-01");
LocalDate.of(2025, 1, 1);
LocalDate.of(today.getYear(), 1, 1);
today.with(MonthDay.of(1, 1));
today.withMonth(1).withDayOfMonth(1);
today.with(firstDayOfYear());

正直「どうでもいいこと」だけど、自分ならどれを選ぶかとか、その理由とかは自分の中で持っておくことをお勧めしたいです。それをコーディング規約にするとか、コードレビューの観点にないのに指摘するとかは個人的には微妙領域。もちろん「今年」であることが重要でそのコードが繰り返し実行されるものなら 2025 のハードコーディングのだけはNGです。なお「どうでもいいこと」に鉤括弧をつけているのは 緊急時の規律 - 日々常々 とか 表出した事象を叩き潰してはいけない - 日々常々 とかと同様の私の中の特殊コンテキストの言葉だから。そのうち書くかもしれません。

ちなみに最後の firstDayOfYear() は TemporalAdjusters さんです。 static-importを前提につくられてるAPIなのでこうですが、しなかったら today.with(TemporalAdjusters.firstDayOfYear()) と文字数も最長になり読みにくくもあるダサいAPIになります。

あ、2月の月末日には TemporalAdjusters.lastDayOfMonth() が使えるので、月末日が必要な時はぜひ思い出してあげてください。 3月1日から minusDays(1) でもいいけどさ。ダサいじゃん。

getDayOfXxx

getDayOfYear() は「その年の日数」です。月の満ち欠けを無視して年を基準に生きる人なら「今日は何日だっけ?」には「X月X日」ではなく「X日」と答えるのが当然で、そういう人が使う日付取得メソッドがこれです。そんな人見たことないけど。

LocalDate には getDayOf から始まるメソッドが3つあります。JSLのAPI仕様を見ても良いのですが、jshellさんで getDayOf まで書いてtabを押したら出てきます。素敵機能。

たぶん多くの人は「 LocalDate から日付を取得したいだけなのになんで getDay() じゃなく getDayOfMonth() なんてしなきゃいけないんだ」と面倒に思っていたりするのではないでしょうか。私は面倒に思ってます。だいたいの文脈において月の満ち欠けを前提にしていきているのが地球人類ですから、 getDay() と言う素直なメソッドで「今日は何日?」に応えてほしいと感じるでしょう。ちなみに今日は8日です。

一週間が重要な業務システムだと getDayOfWeek() は地味に役立ちます。他の2メソッドは int 返ってくるのに、この子は enum DayOfWeek を返してくるところとか我が道を行っていてオシャレですよね。 getDayOfWeek() と似たものに getMonth() があって、この子も enum Month が返ってきます。でこっちには int を返す getMonthValue() があったりします。 getDayOfWeekValue() はない。要らないけど。欲しくなったら DayOfWeek に getValue() したげればいいです。なんかで使ったことあるけど、なんだっけな。忘れました。

今年の経過日数 / 今年の日数

LocalDate.now().getDayOfYear() / LocalDate.now().with(TemporalAdjusters.lastDayOfYear()).getDayOfYear()

これやると 0 になるんですよね。

で「あー小数点だから double 使わなきゃねー」とかで出てくるんだけど、業務コードで double を安全に使ってその安全な状態を維持し続けるのは至難の業。 double は小数点数を扱いますが、固定小数点数でなく浮動小数点数で、そのことを分からないなら使うべきじゃないし、わかっているならそもそもほとんどの場合で使わないと思います。

なので今回みたいな単発書き捨てスクリプトならいいかもだけど、業務では dobule とかは存在しなかったものくらいに扱って、常に BigDecimal を使って丸モードに意識を向けるべきだと思ってます。これは先にあげたような人に押し付けないものじゃなく「規約にすべき」くらい強く思っていることです。私がコーディング規約に関わったところでは入ってるんじゃないかな。コードレビューでも出てきたら警戒レベル上げるし。

まとめ

こういう、役に立つようなたたないような話が技術力に繋がると思っています。裏付け考えてみたり、調べてみたり、動かしたり。 こう言うのを楽しめる人たちと会話できると楽しいって思います。楽しめるべきとなんて思ってないので、楽しめない人がいてもいいと思います。押し付ける気はさらさらありません。棲み分けでいいと思います。

とは言え今後はAIにお任せになっていくので、このまんまの技術は要らないものになっていくんだろうなぁとは思っています。 AIネイティブはAIネイティブで同系統の必要な要素を抽出した別の技術を身につけていくことになるだろうから、過渡期を超えた後の人たちの心配は要らないかなと思ってます。 この過渡期の真っ最中はどうしたらいいんだろうなぁとは思っていますが、。

おまけ:AIの話

私は有償のはJetBrainsAIしか使っておらず、Junieさんは順番待ち。他は手を出していません。なので、まるっとお任せ系(なんて表現が妥当なの?)の手触りはわかっていません。 そっち系で「世界が変わった」みたいなのを横目に見つつ、レイトマジョリティである私はいつごろその世界に行くんだろうなぁって思いながら過ごしてます。 その試行錯誤する暇あったらブランチマイニングしてたいし。 その辺りを知らずにあまりAIのことを語っても的外れになる確信だけはあんだけど、的外れでもいいかぁって思いながら言及してます。

感覚だと これくらい(画像のポストしたツリー) ですが、どうなるやら。腰の重いJTCと揶揄されるようなところが無視できないくらいのビジネスインパクトを与えられればもっと早まるかもですけど。

ちなみに今回のはAIに「 TemporalAdjusters 使わないの?」「 getDayOfYear() は?」とか聞いたら、いろんな言い訳しながら私が書いたのと同じようなコードにはなりはします。聞かないと出してきてくれないです。これに限らず、AIを使う側が存在を知らないと引き出せない系は結構あると思います。これは人相手でも同じなのでそういうものなんでしょうけど。 TemporalAdjusters & getDayOfYear() が良くて between が悪いってことではないですが。ちなみに最初に出してきたコードと TemporalAdjusters & getDayOfYear() では当日の扱いが違うので結果が違ってきます。テストコード書いてそれ通す系のAIならいいんですが、単にそれっぽく動くものだとこの違いがあることを言わずに挙動変えてくるので、その辺が迂闊な人に「AI使えばいいじゃん」みたいなことを業務でやるのは……と思ったけど、その辺が迂闊な人はAI使わなくてもたいして変わらないから別に気にしなくていいやって思いました。

おまけ:AIで仕事はなくならない

「AI使ってるのにまだ仕事しなきゃいけないんだけど!」って嘆き(ネタ)が聞こえたので、ざっと描いたもの。

今までできていなかった本来の仕事ができるようになります。AIを使うことで偶有的複雑さを持ち込むことになるので、その仕事も新たに増えるだろうけど。

アジャイルサムライはいまでも読み返すと「それな」ってなります。システム開発をしていない業界の人にも紹介してみたら「仕事でめっちゃ役立ってる」って言ってて、そうよねーって思いました。

Java21に上げたらテスト通らないんだが

まだJava17の子がいたので、雑にJava21に上げてみたらテスト落ちたの。 「なんでそんなとこ落ちんの?」って感じだったのでメモ。

何変わったんだろ

正規表現の \b の扱いが変わってたっぽいです。

Java17でやるとこうで、

% jshell
|  JShellへようこそ -- バージョン17.0.14
|  概要については、次を入力してください: /help intro

jshell> "あ".matches(".\\b")
$1 ==> true

Java21だとこうなります。

% jshell
|  JShellへようこそ -- バージョン21.0.6
|  概要については、次を入力してください: /help intro

jshell> "あ".matches(".\\b")
$1 ==> false

わお。

ちなみに "a".matches(".\\b") はどっちも true です。

いつ変わったんだろ

API仕様を見比べるとJava18 までは 単語境界 だったのが、 Java19 で 単語境界 : (?:(?<=\w)(?=\W)|(?<=\W)(?=\w)) (単語以外の文字が単語文字を省略するロケーション) に変わっていました。

多分この辺なんだろうなぁと思いつつ、SDKMAN!にもJava18-20はもうないので、挙動での確認は面倒だなぁと。 別にバージョン特定しなくてもいいかなぁと思いながらコミットログ眺めてたらこれが目に入りました。API仕様と同時の19でよさそう。

とりあえず17から21にしたら変わっちゃいます。正規表現を使っているとこのテストは見直しとくといいかもです。

なおAPI仕様の説明は Java24でまた変わる みたいです。

ちなみに

UNICODE_CHARACTER_CLASS したらJava21でもJava18以前と同じ結果の true にはなります。ここに関しては。他も変わるからまぁあれだ。

// Java17, 21どちらも同じ
jshell> Pattern.compile(".\\b", Pattern.UNICODE_CHARACTER_CLASS).matcher("あ").matches()
$2 ==> true

jshell> Pattern.compile(".\\b").matcher("あ").matches()
$3 ==> false

もともとUNICODE_CHARACTER_CLASS つけなきゃ \w が "あ" とかに使えなかったわけで。

// Java17, 21どちらも同じ
jshell> Pattern.compile("\\w", Pattern.UNICODE_CHARACTER_CLASS).matcher("あ").matches()
$4 ==> true

jshell> Pattern.compile("\\w").matcher("あ").matches()
$5 ==> false

\w と \b の対応が取れていなかったのがちゃんと取れるようになったようです。

まとめ

雑に正規表現使ってると踏むかもしれない挙動変更がJava19にありました。

挙動変更は更新追ってなければテストでしか気づけないから、やっぱテストは大事。

バージョン跨いだテストは早めに回しておきたいところ。できるところはやってるんだけど、Gradleのバージョン上げれてなくてできてなかったんだよね……

宣伝

Java24が2025-03-18にGA予定です。

Java24なので次回関ジャバは3/24に行います。

kanjava.connpass.com

Java24の話をしつつも、17以降で「これは実務に影響あったなぁ」とか22以降の「これは使いそうだなぁ」みたいな話をしようと思っています。

自分ひとりから始められる生産性向上の取り組み

1ヶ月前になりますが、2025-01-22 D-Plus Osaka #1 開発生産性を高めたい!関西エンジニアのためのTips共有会 に参加&LTさせてもらってきました。 次回、でぃーぷらすオオサカの2回目は 2025-03-19 に行われるようです。

募集ページをみた時は人数も少なくて、賑やかしLTくらいならできるかなーと思って申し込みました。 東京がメインのコミュニティの大阪初回ということや、知り合いが運営に関わっていると聞いて応援したいなーって気持ちから。 そしたらあっという間にLTも参加も埋まってしまい。「あーこれはやらかしたか……」とか思いつつ、まぁ気にしなくていいかなって。

speakerdeck.com

LT(7分)に対してもりもり詰め込んだ資料となっているので、口頭ではほとんど読み上げもしなかったと思います。 また、灰色部分はスキップゾーン(言及すらせずページチラ見せするだけのやつ)ですが、投影&公開資料には載っていないブラックゾーンなどもあったりします。

こんなの。他にxxページくらい。いやヘッダに「絶対収まらない」とか書くなら最初から作るなとね……。

まぁ3日前にわかっていたことですが。

LTと資料について

私の普段のセッションは会場の反応とか見ながら出し入れするものにしています。 今回はLTの定義もゆるふわな昨今の事情と、はじめてのコミュニティ&少ない枠を取ってしまったと言うことで失敗率を下げようと、「どうとでも調整できるLT」を設計したらこうなりました。 去年10月の京アジャ のLTは我ながらひどかった

「LTらしいLT」は私のイメージで、勢いで押し切るやつです。ライトニングだし。個人的には五指に入る出来だったと自負しています。 他の人とトーンが違って浮いてなかったかなと心配にはなりましたが、懇親会で聞いた限りは大丈夫って言ってくれたからきっと大丈夫だったんでしょう。聞きながらも「この聞き方じゃ大丈夫じゃなくても大丈夫って言うしかないよなぁ」とか思ったりはしましたが、他に聞きようもないから仕方ない。大丈夫だった、うん。資料にも書いてるように計測は大事。観測者効果を気にするのは計測してからだ……ってのも書いたつもりだったのに入ってないな?削ったっけ?まぁいいや。

内容の下敷きは以前書いた「開発時間の内訳を眺めてみよう」です。

irof.hateblo.jp

ピンとくる人も居た ようで。はい、このブログ投稿して 反応もらった あとにPSPの入門本とか読んだので色は濃くなってると思ふ。

原著は1997年、訳書も2001年と流石に古いですが、今でも役に立つエッセンスは結構あります。とは言えかなりの読み替えが必要なので、おすすめできるかっていうと、うーん。

他にも具体的なやり方の書いているPSP入門とは違って考え方系ですが、継続的デリバリーのソフトウェア工学にも通じるところがあります。

こっちはぜひ読みましょうなおすすめ本。 昨日の関ジャバ で増田さんのセッションでも紹介されていました。 特に「ソフトウェア工学?なんか堅苦しくて重々しくて現実だと使いづらいんじゃ?」とか思うような人。つまり読む前の私。

さて、話の内容については、スライドに書いています。というかLTで口頭で話したものの方が情報量が少ないくらいです。 私が「ふつう」にやっていること のうち、人におすすめしてもいいと思える程度に言語化と実績のあるプラクティスを抜粋した、取り組みやすいもののつもりです。

スライドだけで伝わる構成(普段はあまりしない)としていたこともあってか、思いの外RPやいいねが伸びてびっくりしました。

あと、1ヶ月経とうかというころにすどうさんがnote書いてくれてました。

note.com

よくスライドだけでここまで汲み取れるなぁと感心しました。むしろ現地にいた人の方がLTのトーンに引きずられて内容にノイズかかってるまであるのではないだろうか……。

直後以外にも言及されるのっていいですね。

生産性について

「生産性」って言葉を現場で聞くのはネガティブな状況が多いと思います。良い時って気にもしないものだと思います。 そしてこの言葉を出すのはパワーバランス的に強者(少なくとも言われている方はそう感じる相手)なことも多く、「遅れてる!」とか言って責められるのとセットな感じ。まぁ「遅れ」なんてないんですが。

irof.hateblo.jp (7年前。間違ったことは書いてないつもりだけど、取り扱い注意ではあると思う。)

また「生産性」の話題は資料でも書いていますが、組織的な話になることが多いと思います。 だからと言って「組織任せ」のようなスタンスはちょっと違うと思うんです。ややもすれば生産性と個人の取り組みは関係がない、あっても誤差だと言われることすらあります。 ある面では統計などが示しているようですからそれは事実なのかもしれませんが、システム開発業界はコンピュータの力が借りられることもあってか「人による生産性の差はXX倍」なんて話もあります。 こちらも眉唾なもので全てのコンテキストで当てはまるものではありませんが、人によっては倍どころか「(時間など実務制約の元で)解決可能か不可能か」という比較すらできない差がある(0に何倍しても0だよねって話)のは動かし難い事実です。

……とかから話を広げると帰ってこれないので切り上げるとして。

複数人で開発する以上、最終的な生産性は組織に強く依存するのでそちらを外すことはできませんが、組織の生産性だけよくしようとしたところで、個人が応えられなければ厳しいと思います。 明確に言語化できているわけではないですが、私の脳内モデルでは組織の生産性は道路で、個人の生産性は走る車の小回りや加速力や最高速になります。 道路が狭く入り組んでいればいくら最高速が出せる車でも時間はかかりますし、道路が工事や災害で断絶していれば到達できなかったり徒歩になったりします。 かたや広く平坦な高速道路が整備されていても、速度が出せないなら宝の持ち腐れです。どちらも必要なのです。 そして個人の生産性は一朝一夕に上げられるものでもありません。「走れる状況で走れない」のようなのは私は嫌です。嫌だと思うなら備えるしかありませんし、備える以上は普段から使いたいものです。この辺りは「緊急時の規律」に通じます。

irof.hateblo.jp

と言うことで、普段からやっていこ。です。先にあげたように『「遅れ」なんてない』と嘯くのであれば、それに見合う程度に無理筋じゃない言い訳ができる程度のことはやらねばなるまいって思います。 そしてひとりカンバンのように「普段から組織でやってるプラクティスを個人に縮小適用して鍛錬し、それを組織に逆適用しちゃえばいいじゃん」みたいな相互適用をバンバンしていけばいいかなって思ってます。良さげなプラクティスに取り組む際、「聞いただけ」じゃなく「(自分一人では)やったことある」だけでも全然違うものだったりします。てことでスライドの最後にもあるやつで締め。

やってもうまくならないことはあるけど、それはそれ。