ソフトウェアエンジニアの雑記

日々思ったことをまとめます

朝ラン、読書などで行うデジタルデトックスが結構大事かもなって話

デジタルデトックスとは

起きているときは、SNSやニュースの通知が気になって一日中スマホの画面を無意識にタップしてしまう。そんな「ずっと接続された状態」が日常になっている今、デジタルデトックスは実は特別なものじゃありません。スマホやPCから意図的に距離を置き、心と頭を「オフ」にする時間のことです。以前は完全に断つことばかり考えていましたが、今は生活に自然に溶け込む小さな習慣として取り入れる方が、ずっと心身にとって優しく実践しやすいと実感しています。

朝ラン

私がが実践しているのは、平日30分弱、休日は1時間弱の朝ランです。これが意外にも「強制的なデジタルデトックス」として時間になっていると思います。この時間は結構大事で考え事のまとめる時間にもなっています。ポッドキャストやオーディオブックなどをBGMに聞きながら走るんですが、いつのまにか思索に耽っています。

なぜウォーキングではなくランニングを選ぶのかというと、歩きながらだとついついスマホを触ってる自分に気づきます。ランニングだと、画面はブレるし、文字は打ちにくいので自然とスマホを触らなくなります。この「自然と」が結構大事だと思っています。朝ランを続けて、近い将来にはフルマラソンも走れたらと思っています。

また、朝ランは「リズム運動」というものに分類されるらしく、「リズム運動」のときに幸福ホルモン「セロトニン」がよく出るらしいです。さらに日光を浴びながら走るとより効果的らしいです。朝から割と負荷の高いことをやっていますが、心なしかメンタルも安定していると思います。

読書

デジタルデトックスのもう一つの柱が読書です。平日は7〜15分程度、休日は15〜30分以上を目安に本を読んでいます。

難しい本を完璧に読もうとする必要は全くなくて、ただ頭をスッキリさせながらページをめくる時間が気に入っています。その結果として自然と一般教養が身についていくのは嬉しい副産物です。スマホの通知音もなく、集中してページを追いかける時間は、脳のスイッチを入れるのにちょうどいいペースセットになります。短くても毎日続ければ、ちゃんと自分の知識の土台になっていきますよ。

デジタルデトックスの効能

こうした習慣を続けた結果、強く感じているメリットは主に3つです。

メンタルヘルスの安定 スマホの画面から離れるだけで、あの「何か見逃してるかも」という不安感や、他人の情報と比べてしまうストレスが劇的に減ります。

思考時間の確保 アルゴリズムに流されるのではなく、「自分がどう思うか」を深く考える時間を持てるようになりました。他人の意見やニュースに振り回されず、自分のペースで考えを巡らせられるのは自信につながります。

情報洪水から脳を守る(脳の休養) 無制限に流れてくる通知や記事は、脳にとってはただのノイズです。あえて画面をオフにして休ませることで、次の日のパフォーマンスや集中力をしっかり維持できています。

継続が大事

これらの習慣を試して一番気づいたのは、「続けないと何の意味もない」という当たり前の真理です。でも、やみくもに頑張ろうとすると必ず倒れます。継続するためのコツは、ハードルを自分で下げることです。

例えば、雨の日は無理に走りません。「シューズのメンテナンスが面倒だし、濡れて重くなるだけ」って割り切ります。また、慣れてきて「もっと頑張ろう!」と強度や時間を上げすぎないことも大事です。僕も過去に、調子が良くなって無理に距離や負荷を上げたら、すぐに行き詰まって挫折した経験があります。長期的に見て「今日はちょっと軽めに」くらいの余裕を持つほうが、結局は長く続けられます。完璧を目指さず「やれた日だけを積み重ねる」考え方が、想像以上に力を発揮します。

まとめ

デジタルデトックスって、スマホを封印したり電波のない場所に逃げたりする大それたことじゃありません。朝の30分ランにポッドキャストを流し、寝る前の15分で本を開く。そうした小さな「オフの時間」をいかに生活に組み込むかが、現代を生き抜くための大切な武器だと思います。

特別なことをせずに、日々の中で如何に継続できるかがポイントだと思います。私は、たまたま「朝ランと読書」だっただけで、農作業の方もいれば、通勤のときにスマホを触らないでもいいとおもいます。自分にあった方法を探してみてください。

単体テストはシンプルに書くべきだと思う

単体テストは「将来の開発速度を維持する投資」につきます。しかし現場では、テストコードが逆に保守の重荷となり、小さな修正にも数日かかるケースは珍しくありません。多くの現場での根本原因は「テストを複雑」ということが結構あります。

単体テストをシンプルに保ちながら品質と開発効率を両立するために思っていることや気をつけることを書いていきます。

そもそもビジネスロジックは複雑

実際のビジネスロジックは、組織のルール、状態遷移、既存システムとの整合性など、 本質的に複雑な要素を内包しています。そのため、テスト側まで過度に詳細や依存関係を固定化させると、「何を確認したいテストか」が置き去りにされ、テストコードの読み解きおよび修正コストが急増します。

重要なのは、ビジネスの意図を明確に分離し、テストは「入力 → 期待される出力/状態変化」へのシンプルなマッピングに徹することです。過度なモックや偽データの積み重ねは避け、現実のビジネス要件に即した核心的ケースに絞りましょう。テストが複雑になる時ほど、まずは「本当にその詳細まで確認必要があるか?」を自問することをお勧めします。

APIは複雑でない方が良い

テストが複雑になる根本原因は、往々にしてテスト対象のAPI(公共インターフェースやメソッド)自体が複雑になっていることにあります。「テストを書くのが難しいコード」は、そもそも設計の見直しが必要なサインです。

APIをシンプルに保つことで、テストのセットアップが軽くなり、失敗時のデバッグも容易になります。もし現在のAPIをテストしにくいと感じたなら、フレームワークの工夫やテストコード側で辛さを吸収するより先に「責務の分割」や「パラメータ設計の見直し」を実行しましょう。テストはコードを悪くする言い訳になってはいけません。シンプルで呼び出しやすいAPIは、テストに限らず利用者全員にとって資産です。

パラメータテストで網羅的にテストをする

網羅的にテストをする場合は、手動で複数のテストケースを列挙するより「パラメータ化された単体テスト(Parameterized Tests)」を活用すべきです。 冗長なテストコードは、ビジネスロジックと同じく扱いづらいものになります。

現代のテストフレームワークでは、入力値と期待値の組み合わせをデータ駆動で定義でき、DRY原則が自然に守られます。境界値やエラーケースを体系的にテストできる上、新しい要件や追加仕様が生まれてもデータフローだけでカバレッジが拡張可能です。複雑なロジックを単純なテストケースの集合でカバーするには、パラメータテストが最も効果的かつ保守性の高い武器になります。

不要になったテストコードは削除する

「テストは書けば終わり」ではありません。リファクタリングや機能変更、仕様の削除により、かつて有効だったテストが「死んだテスト」と化すことは日常的に起こり得ます。

残った不要なテストは、CIの実行時間を無駄にし、失敗レポートを肥大化させ、「テストが通っている=安全」という誤った安心感を生みます。加えて、次の開発者が意味を推測したまま修正を加え、バグの温床になるリスクもあります。テストコードも成果物と同じく、定期的にレビューし、不要になれば潔く削除しましょう。テスト実行時間の計測やCIフィルタを活用し、「実行する価値のあるテスト」だけが残る環境を目指すべきです。

テストコードが書かれていると、不要なコードを削除しやすくはなると思います。不要なコードは積極的に削除していきましょう。もちろん削除するときにテストもしましょう。

まとめ

単体テストは「品質の盾」ではなく、「開発を加速するための潤滑油」であるべきです。 ビジネスロジックは複雑になっていくとは思います。我々プログラマが扱える変数であるAPIやテストコードをなるべくシンプルに保つのは大切なことです。

ビジネスロジックの複雑さを受け入れつつAPIはシンプルに保ち、パラメータテストで体系的にカバーし、不要なコードは躊躇なく削除する。この循環を回すだけで、テスト資産は「負債」から「投資」へと変わり続けます。シンプルさこそが、長期的なサービス開発の中で生産性を維持する武器としてシンプルなユニットテストを育てて行ければと思います。

Appendix.

サンプルコード

最近のJavaでは、JUnit5でかなり単体テストが書きやすくなりました。@Nested,@ParameterizedTest,@RepeatedTest,@TestFactory,@DisplayName,@TestInstanceの実用的なアノテーションが追加され、特にパラメータライズドテストなどは使いやすいです。下記サンプルを書いておきましたので、

/**
 * JUnit5 主要アノテーションのサンプル
 *
 * テスト対象: 簡単な電卓クラス Calculator
 */
@DisplayName("Calculator のテスト")
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // クラスにつき1インスタンスを使い回す
class CalculatorTest {
 
    private Calculator calculator;
    private int repeatCounter = 0; // PER_CLASSなのでインスタンスフィールドを共有できる
 
    @BeforeAll
    void setUpAll() {
        // PER_CLASSモードなので @BeforeAll を非staticメソッドとして書ける
        System.out.println("=== テスト全体の開始 ===");
    }
 
    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }
 
    // ------------------------------------------------------------
    // @DisplayName: テストに分かりやすい表示名をつける
    // ------------------------------------------------------------
    @Test
    @DisplayName("1 + 1 は 2 になること")
    void testAddition() {
        assertEquals(2, calculator.add(1, 1));
    }
 
    // ------------------------------------------------------------
    // @ParameterizedTest: 複数の入力パターンを1つのテストメソッドで検証
    // ------------------------------------------------------------
    @DisplayName("加算のパラメータ化テスト")
    @ParameterizedTest(name = "{0} + {1} = {2}")
    @CsvSource({
            "1, 1, 2",
            "2, 3, 5",
            "-1, 1, 0",
            "0, 0, 0"
    })
    void testAdditionParameterized(int a, int b, int expected) {
        assertEquals(expected, calculator.add(a, b));
    }
 
    @DisplayName("正の数の判定テスト")
    @ParameterizedTest(name = "{0} は正の数")
    @ValueSource(ints = {1, 5, 100})
    void testIsPositive(int value) {
        assertTrue(calculator.isPositive(value));
    }
 
    // ------------------------------------------------------------
    // @RepeatedTest: 同じテストを指定回数繰り返し実行する
    // (乱数やリトライが絡む処理の安定性確認などに利用)
    // ------------------------------------------------------------
    @DisplayName("カウンターのインクリメントを5回繰り返す")
    @RepeatedTest(5)
    void testIncrementRepeatedly(RepetitionInfo repetitionInfo) {
        repeatCounter++;
        System.out.printf("実行回数: %d / %d%n",
                repetitionInfo.getCurrentRepetition(),
                repetitionInfo.getTotalRepetitions());
        assertEquals(repetitionInfo.getCurrentRepetition(), repeatCounter);
    }
 
    // ------------------------------------------------------------
    // @TestFactory: 実行時にテストケース(DynamicTest)を動的に生成する
    // ------------------------------------------------------------
    @DisplayName("動的に生成する減算テスト")
    @TestFactory
    Collection<DynamicTest> testSubtractionDynamic() {
        List<DynamicTest> tests = new ArrayList<>();
        int[][] cases = {{5, 3, 2}, {10, 4, 6}, {0, 0, 0}};
 
        for (int[] c : cases) {
            String name = String.format("%d - %d = %d", c[0], c[1], c[2]);
            Executable executable = () -> assertEquals(c[2], calculator.subtract(c[0], c[1]));
            tests.add(dynamicTest(name, executable));
        }
        return tests;
    }
 
    // ------------------------------------------------------------
    // @Nested: 関連するテストをグループ化し、階層構造で整理する
    // ------------------------------------------------------------
    @Nested
    @DisplayName("除算に関するテスト")
    class DivisionTests {
 
        @Test
        @DisplayName("正常な除算ができること")
        void testDivision() {
            assertEquals(2, calculator.divide(10, 5));
        }
 
        @Test
        @DisplayName("0で割ると例外がスローされること")
        void testDivisionByZero() {
            assertThrows(ArithmeticException.class, () -> calculator.divide(1, 0));
        }
 
        // ネストしたクラスの中にさらにネストも可能
        @Nested
        @DisplayName("小数を扱う除算")
        class DecimalDivisionTests {
 
            @Test
            @DisplayName("割り切れない場合も計算できること")
            void testNonExactDivision() {
                assertEquals(3, calculator.divide(10, 3));
            }
        }
    }
 
    @AfterAll
    void tearDownAll() {
        System.out.println("=== テスト全体の終了 ===");
    }
}
 
/**
 * テスト対象のシンプルな電卓クラス
 */
class Calculator {
    int add(int a, int b) {
        return a + b;
    }
 
    int subtract(int a, int b) {
        return a - b;
    }
 
    int divide(int a, int b) {
        return a / b;
    }
 
    boolean isPositive(int value) {
        return value > 0;
    }
}

@TestFactoryは利用が少し難しいんですけど、それ以外は割と直感的に使えると思うので、積極的に使っていくのが良いと思います。

JavaでDataFrame操作をDFLibで試す

JavaでもDataFrameを使いたいとおもって、探すと、TableSawとかDFLibとかdataframe-ecがありました。軽量ライブラリのDFLibを使ってみようと思います。

dflib.org

サクッと試したいのでjbangで試すこととします。jbangの記事はこちら

サンプルソースはこちらになります。

DFLib Sample ( DataFrame implements Java) · GitHub

DataFrameに読み込ませて、ソートする

対象のデータファイルは下記のようになっています。

data.tsv

a   b   c   d
--  --  --  --
4   5   6   a
40  41  42  a
46  47  48  a
52  53  54  b
58  59  60  b
64  65  66  c
94  95  96  c

Javaコードは下記のようになります。DFLibはデータロード時にデフォルトでは文字列として扱います。数値として扱う場合は、intCol()で設定する必要があります。例の場合は、カラムcを数値として扱うようにします。こうするとソートが機能します。

///usr/bin/env jbang "$0" "$@" ; exit $?

//DEPS org.dflib:dflib:1.3.0
//DEPS org.dflib:dflib-csv:1.3.0
//JAVA 25+

import org.apache.commons.csv.CSVFormat;
import org.dflib.DataFrame;
import org.dflib.Printers;
import org.dflib.csv.Csv;

void main(String... args) {
    CSVFormat csvFormat = CSVFormat.Builder.create().setDelimiter("\t").build();
    DataFrame dataFrame = Csv.loader().format(csvFormat).header("a","b","c","d").intCol("c").offset(2).load("data.tsv").sort("c",false);
    IO.println(Printers.tabular(100,100).toString(dataFrame));
}

====出力結果

a  b   c d
-- -- -- -
94 95 96 c
64 65 66 c
58 59 60 b
52 53 54 b
46 47 48 a
40 41 42 a
4  5   6 a

上記のようにソートができます。

2つのファイルはDataFrameに読み込ませて、結合する

上記のdata.tsvとtype.tsvをDFLibで結合する。ファイルは下記。

// type.tsv

type    name
--  --
a   TypeA
b   TypeB
c   TypeC

DataFrameを別々に読み込みませて、カラムaとカラムtypeを指定して結合してみます。

void main(String... args) {
    CSVFormat csvFormat = CSVFormat.Builder.create().setDelimiter("\t").build();
    DataFrame dataFrame = Csv.loader().format(csvFormat).header("a","b","c","d").intCol("c").offset(2).load("data.tsv").sort("c",false);
    DataFrame dataFrameType = Csv.loader().format(csvFormat).header("type", "name").offset(2).load("type.tsv");

    DataFrame selectFrame = dataFrame.innerJoin(dataFrameType).on("d", "type").select();
    IO.println(Printers.tabular(100,100).toString(selectFrame.cols("a","b","c","name").select()));
}

a  b   c name 
-- -- -- -----
94 95 96 TypeC
64 65 66 TypeC
58 59 60 TypeB
52 53 54 TypeB
46 47 48 TypeA
40 41 42 TypeA
4  5   6 TypeA

上記のように結合できました。出力時にカラムも絞ることができます。

まとめ

Javaでも簡単にDataFrameが使えるようになります。グラフ描画もできますし、そこそこ使えるかなと思います。

ポエム

Java25になって、mainメソッドとかなくても簡単に書けるようになっているのは、便利です。void main() でコードが書けるので、サンプルコードもシンプルになりますし、jbangの機能も十分に使えるので、どんどん使っていければと思いますー。

MarpでMarkdownでスライドを作る

勉強会や登壇でスライドを作るのって結構大変で、ツールごとにお作法みたいなものがあって割と面倒だなーと思っておりました。Markdownで書けると最高だなって思ってましたが、なんかツールがあるっぽいので、ご紹介です。

Marp

Markdownでスライドが作れるMarpです。

marp.app

CLIとVSCodeの拡張がありますので、好きな方を使うと良いと思います。

Marp記法

基本的にMarkdownですが、先頭にMarpであることを宣言します。

---
marp: true
theme: sample-theme
paginate: true
---

themeは、CSSで記述します。

/* @theme sample-theme */
/* モダンでクリーンなテーマ */

@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700;900&family=Inter:wght@400;500;700&display=swap');

section {
  background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
  font-family: 'Noto Sans JP', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  color: #1a1a1a;
  padding: 60px 80px;
  font-size: 20px;
  line-height: 1.7;
}

h1 {
  color: #c41e3a;
  font-size: 56px;
  font-weight: 900;
  letter-spacing: -0.02em;
  margin-bottom: 20px;
  line-height: 1.2;
  border-left: 8px solid #c41e3a;
  padding-left: 24px;
}
...
...
...

VSCodeでのサンプル

VSCodeのサンプルはこちらになります。(最近、JavaでDataFrameを触ってて、結構いいので紹介スライドを作っているところです) VSCodeの拡張はリアルタイム反映なので、作業もサクサクです。

まとめ

PDF出力もPDF,HTML,PowerPointと十分な出力を標準で装備していますので、凝ったスライドでなければ、十分なツールになります。

dokcer composeを使う時に、プロジェクト名(name:)を入れよう

docker composeはローカル環境構築に大変便利ですが、複数プロジェクトで命名が競合することが多々あります。(portも競合しますが、それは仕方ない)

docker compose を利用する際には、必ずプロジェクト名(name:)を入れましょうってだけです。なお、container_name:が設定されている場合は、そちらが優先されます。

name: example-project # <- これを入れるだけ
services:
  adminer:
     image: adminer:latest
     ...
     ...

  db:
     image: mysql:8.4
     ...
     ...

確認してみます。

$  docker compose ps | awk '{print $1}'

NAME
example-adminer-1
example-db-1

対象は、cotainer_name, volume_name, network_name になります。複数プロジェクトを運営されているチームではやっておくと良いかと思います。

ThymeleafとAlpine.jsの簡単な連携

ThymeleafとAlpine.jsの簡単な連携の小ネタです。Thymeleafには、th:inlineというインライン処理というものがあり、指定した形式に加工してくれる便利なものがあります。

例えば、下記のようなJavaのMapデータがあります。

@Controller
@RequestMapping("sample")
class SampleController {
    @GetMapping
    public String index(Model model){
        Map<String,String> userMap = Map.of("user", "Taro");
        model.addAttribute("user" , userMap);
        return "index";
    }
}

Thymeleafの方でJSに渡したいとしたときに下記のような記述でJSで使えるように展開してくれます。(JSONにしてくれます)

<script th:inline="javascript">
   const userMap = /* [[ ${user} ]] */  { user: 'Taro'};
</script>

Alpine.jsで使用するときに、x-dataの中にThymeleafから直接データを入れようとすると、相性があんまりよくないので、このThymeleafのインライン処理を経由してバックエンドのデータを渡します。

<script th:inline="javascript">
   const userMap = /* [[ ${user} ]] */  { user: 'Taro'};
</script>

<div x-data="{data: userMap}"></div>

Alpine.js側のx-dataは、JS経由でデータをもらいます。Alpine.jsとの連携に割と便利なので、サクッと使い時にはよさそうです。

SpringBootでLocalDateTime.now()などの時刻管理を使用したときに、ユニットテストできるようにする方法

はじめに

SpringBootを利用していると、静的メソッド(LocalDateTime.now()など)を使用したくなる場面があります。静的メソッドはユニットテストをしづらくする側面があるのでなるべく使用を控えたいので、その回避方法になります。

目次

要約

要約としては、下記の2点になります。

  • java.util.Clockを利用する
  • @ConfigurationでBean化する
  • 利用したいクラスでDIする

静的メソッドのメリット・デメリット

静的メソッドは便利ですが、状態を持つと途端に面倒なツールに早変わりします。

デメリットとしては、

  • DIとの相性が悪い
  • テスタビリティが低い(PowerMockやPowerMockito、Mockito.mockStaticなどを使用する必要があり、十分ではない)
  • グローバルな情報を参照していると予期しない副作用のリスク
  • マルチスレッド処理での懸念

などなど、小さいアプリケーションではあんまりデメリットにはならないですが、ユニットテストが威力を発揮するような中〜大規模になってくると状態をもった静的メソッドは邪魔にしかなりません。よくあるのが、LocalDateTime.now()などをコアロジックで直接書いてしまうと、ユニットテスト時にMockito.mockStaticやPowerMockitoなどを使用しないといけなく不便になります。

特に、テスト対象のメソッド内で複数スレッドを作成し、その中でLocalDateTimeを呼んでいる場合は、Mockito.mockStaticで設定しても、範囲外なので設計を大きく直す必要があります。

Clock を使った解決策

1. Clock のBean定義

まず、ClockをSpringのBeanとして定義します。

@Configuration
public class ClockConfig {
    
    @Bean
    public Clock clock() {
        return Clock.systemDefaultZone();
    }
}

2. Service層での利用

ClockをDIして使用します。

@Service
@RequiredArgsConstructor
public class ClockServiceImpl implements ClockService {
    private final Clock clock;

    @Override
    public LocalDateTime getAfter5Minute() {
        return LocalDateTime.now(clock).plusMinutes(5);
    }
}

3. テストでの活用

テストでは、固定された時刻を返すClockを使用できます。

@ExtendWith(MockitoExtension.class)
class ClockServiceImplTest {

    @InjectMocks
    private ClockServiceImpl clockService;

    @Spy
    private Clock clock = Clock.fixed(Instant.parse("2026-01-01T00:00:00z"), Clock.systemUTC().getZone());;

    @Test
    void getNow() {
        LocalDateTime now = clockService.getAfter5Minute();
        assertEquals(LocalDateTime.parse("2026-01-01T00:05:00"), now);
    }
}

上記のようにすれば、ミリ秒やマイクロ秒の差でのテスト失敗が発生しません。

まとめ

Spring BootのDIの思想に沿ってClockを活用することで下記の恩恵に預かれます。

  • テストが容易になる
  • 時刻を完全に制御できる
  • タイムゾーンを明示的に管理できる
  • コードの保守性が向上する

小さな変更ですが、テスタビリティと保守性に大きな違いをもたらします。ぜひ実践してみてください。