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

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

JavaのCheckStyleで除外設定をする方法

Javaを使っていると静的解析にCheckStyleを使うことが多いと思います。ただ自動生成コードなどはSuppressionFilterで対象外にしますが、ファイル数が多いと処理に時間がかかります。これはファイルを検索したあとにSuppressionFilterに設定してあるファイルかどうかのチェックを行っています。 これは、SuppressionFilterが、ファイル1つ1つで何をチェック対象から外すかという細かい制御機能を持っているからです。

自動生成コードを使用している場合、ディレクトリ配下すべてが自動生成コードであることはよくあります。その場合はCheckStyleディレクトリごと読み込む必要がないので、これを行うのは、BeforeExecutionExclusionFileFilterになります。

これで読み込むファイル数を減らすことができるので、CIの実行時間が短くなります。

<module name="Checker">

  <module name="RegexpOnFilename">
    <property name="fileNamePattern" value="^[A-Z].*"/>
    <property name="match" value="false"/>
    <message key="regexp.filename.mismatch"
      value="File name must start with an uppercase."/>
  </module>

  <module name="BeforeExecutionExclusionFileFilter">
    <property name="fileNamePattern" value="module\-info\.java$"/>
  </module>

</module>

Javaコンパイル時にStackOverflowがでる

Javaで開発をしていると、LombokやMapStructなどのAnnotationProcessorでコードを生成していたりする結構なファイルが裏では生成されていて、コンパイル時にstackoverflowがでることがあります。

....
Caused by: java.lang.StackOverflowError
....

基本的には、スレッドやコンパイラスレッドのスタックサイズを増やすことで解消します。Gradleでの設定は下記のように行います。

gradle.propertiesファイルに下記を設定します。

# デフォルトは-Xss512k
org.gradle.jvmargs=-Xss1024k

になります。

SpringBootでLiveReloadを拡張機能なしで動作させる

ChromeFirefoxのLiveReloadの拡張機能の開発が止まっており、拡張機能なしでも動作させる方法をメモしておきます。

build.gradle

build.gradleの依存関係は下記になります。Mavenの方は適宜読み替えてください。

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
}

org.springframework.boot:spring-boot-devtoolsの中に、livereload.jsが同梱されていますので、これを使います。

Thymeleafのコード

今回は、Thymeleafのみで完結します。(JSPでもFreeMarkerなどでも可) scriptタグを1つ追加するのみとなります。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
  <!--/* 下記の1行を追加するのみ */-->
  <script src="http://localhost:35729/livereload.js"
          th:if="${@environment.getProperty('spring.devtools.livereload.enabled')}">
  </script>
</head>
<body>
    <h1>Live Reload 更新されればできている</h1>
</body>
</html>

動作確認

SpringBootを起動してブラウザを開いてあとに、HTMLを更新してみます。

thymeleaf-livereload-demo

上記のように、自動で反映されています。

注意

このままだと、本番環境でもコードがでてしまいますので Thymeleaf側で制御を行います。scriptタグ内に、下記の記述をしておきます。

th:if="${@environment.getProperty('spring.devtools.livereload.enabled')}"

これでbootRunで起動しなければscriptタグは出力されません。

まとめ

spring-boot-devtoolsのドキュメントでもあまり詳細には書いてないのですが、LiveReloadは便利なのでぜひ使ってください。

JBangファイルをIntelliJで補完するために、ディレクトリは"src/main/java"配下にする

タイトル=結論なんですけど、JBangをIntelliJで編集するには、src/main/javaに置かないとシンタックスハイライトも補完もされませんでした。また忘れるかもなんで、一応メモです。

技術の紹介

JBangは、Javaスクリプトを簡単に実行できるツールです。従来のJavaプロジェクトのような複雑なセットアップを必要とせず、単一のJavaファイルで依存関係を管理し、即座に実行することができます。

IntelliJ IDEAは強力なJava開発環境ですが、JBangファイルに対して適切なコード補完や構文ハイライトを提供するには、デフォルトでは、特定のディレクトリ構造を使用する必要があります。

1. プロジェクト構造の設定

your-project/
├── src/
│   └── main/
│       └── java/
│           ├── Example.java
│           └── AnotherScript.java
└── README.md

2. JBangファイルの作成

src/main/javaディレクトリ内にJBangファイルを配置します:

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS com.fasterxml.jackson.core:jackson-core:2.14.2

import com.fasterxml.jackson.core.JsonFactory;

public class Example {
    public static void main(String[] args) {
        System.out.println("Hello JBang!");
        // IntelliJでここに補完が効きます
    }
}

3. IntelliJでの開き方

  1. IntelliJ IDEAでプロジェクトルートを開く
  2. src/main/javaディレクトリがソースフォルダとして認識される
  3. JBangファイルを編集する際に、完全なコード補完が利用可能

任意のディレクトリに配置する場合

任意のディレクトリ内をJavaプロジェクトとして認識させるのには、下記の操作が必要になります。

  1. IntelliJでプロジェクトを開く
  2. File > Project Structure > Modules
  3. Sourcesタブで任意のディレクトリをSourcesとして指定
work/
├── src/
│   └── main/
│       └── java/   <-- ここをSource指定を行う
│           ├── Example.java
│           └── AnotherScript.java
└── README.md

まとめ

JBangでIntelliJの強力な開発支援機能を活用するには、src/main/javaディレクトリ構造を行うとIntelliJの設定が簡略化できます。IntelliJを使用すると、

など、開発における優れた体験を享受することが可能になります。JBangの手軽さとJavaの堅牢な開発体験があるのはとてもいいので、これからも使っていこうと思います。

AirdropのOSS版「LocalSend」でMac・Linux・Windowsも簡単ファイル共有をする

個人の開発環境が、MacBookPro M4 ProとデスクトップLinux(Linux Mint)を使用しており、ファイルの共有でいいものがないかなーと探したら、LocalSendというすごい便利なものがあったので、ご紹介です。

LocalSend: Share files to nearby devices

LocalSendとは

LocalSendは、完全無料のオープンソースクロスプラットフォームファイル共有アプリです。AppleAirdropのような体験を、すべてのOSで実現します。

主な特徴:

LinuxMacでのファイル共有

これまで異なるOS間でのファイル共有は面倒でした。

従来の方法の問題点:

  • メール添付(容量制限)
  • クラウドサービス経由(アップロード→ダウンロード)
  • USBメモリ(物理的な移動が必要)

LocalSendなら:

  • アプリを開いて送信先を選ぶだけ
  • 最大転送速度40MB/s以上
  • ファイルサイズ制限なし(999ファイルまで一度に送信可能)

Airdropとの違い

機能 Airdrop LocalSend
対応OS Apple製品のみ 全OS対応
料金 無料(Apple製品購入が前提) 完全無料
オープンソース
インターネット接続 不要 不要
設定の簡単さ 自動検出 アプリを両端で起動
セキュリティ 高い 同等(HTTPS暗号化)

LocalSendの魅力: - クロスプラットフォーム対応 - オープンソースで透明性が高い - 永続的に無料 - シンプルで直感的なUI

こんな人におすすめ: - MacWindowsLinuxの複数OS環境で作業する人 - プライバシーを重視するユーザー - 無料でシンプルなファイル共有ツールを探している人

まとめ

LocalSendは「すべてのデバイスで使えるAirdrop」として、異なるOS間のファイル共有がかなり便利なものになります。両デバイスでアプリを開いておくだけで、同じWi-Fiネットワーク内で瞬時にファイル共有できます。Android, iOSなどでも使えるようなのでぜひ使ってみてください。

開発者目線だと、UIはDart、通信の暗号化まわりはRustで作られているところはポイント高いなーと思いました。通信は、REST API + HTTPSで行っているようで、実装部分も時間があったら読んでみようかと思います。

公式サイト

https://localsend.org

SpringBoot x ShedLockで簡単に同時実行制御を行う

SpringBootでバッチ処理を書くときに、SpringBatchが選択肢に上がると思いますが、設定だったりクラスを継承したりと面倒です。確かに中断後から処理を開始してくれたりと便利な部分もあるのですが、SpringBatchの仕様理解とかが必要になってきます。もっとカジュアルに同時実行制御を行いたい場合に、ShedLock が選択肢に入ってくると思います。今回は、簡単な紹介になります。

コードはここにおいておきます。GitHub - ko-sasaki/springboot-shedlock-example

build.gradle

依存関係は下記のようになります。(mavenの方は読み替えてください)

plugins {  
    id 'java'  
    id 'org.springframework.boot' version '3.4.8'  
    id 'io.spring.dependency-management' version '1.1.7'  
}  
  
group = 'net.ksasaki.sample'  
version = '0.0.1-SNAPSHOT'  
  
java {  
    toolchain {  
       languageVersion = JavaLanguageVersion.of(21)  
    }  
}  
  
configurations {  
    compileOnly {  
       extendsFrom annotationProcessor  
    }  
}  
  
repositories {  
    mavenCentral()  
}  
  
dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter-web'  
    compileOnly 'org.projectlombok:lombok'  
    developmentOnly 'org.springframework.boot:spring-boot-docker-compose' // (1)
    developmentOnly 'org.springframework.boot:spring-boot-devtools'  
    runtimeOnly 'org.postgresql:postgresql'  
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'  
    annotationProcessor 'org.projectlombok:lombok'  
    testImplementation 'org.springframework.boot:spring-boot-starter-test'  
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'  

    
    implementation 'net.javacrumbs.shedlock:shedlock-spring:6.9.2'  // 追加
    implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:6.9.2'    // 追加


}  
  
tasks.named('test') {  
    useJUnitPlatform()  
}

追加が必要なのは、この2行になります。

環境まわり

今回DBはPostgreSQLを使用します。SpringBootは、開発モードでもアプリケーション起動時にdocker composeファイルを指定すると立ち上げてくれる便利な機能があります。今回はそれを使用します。( 設定ファイルの(1)の依存関係の追加が必要になります。)

compose.ymlは下記のように設定します。

name: shed-lock  
services:  
  db:  
      image: "postgres:latest"  
      container_name: "shed-lock-db"  
      environment:  
          POSTGRES_USER: "shedlock"  
          POSTGRES_PASSWORD: "shedlock"  
          POSTGRES_DB: "shedlock"  
      ports:  
      - "5432:5432"  
      volumes:  
        - database:/var/lib/postgresql/data  
        - ./postgres/init:/docker-entrypoint-initdb.d // init.sqlは後述
      networks:  
        - shedlock-network  
  
volumes:  
  database:  
networks:  
  shedlock-network:

基本的なdocker composeの設定になります。

application.ymlは下記のように設定します。

spring:
  application:
    name: schedule
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/shedlock
    username: shedlock
    password: shedlock

  docker:
    compose:
      file: compose.yml
  task:
    scheduling:
      pool:
        size: 10    // scheduleはデフォルトだと1スレッドしか立ち上がらない
  main:
    keep-alive: true  // virtual threadのみが動作していてもJVMが終了しない設定
  threads:
    virtual:
      enabled: true

注意点は、スレッド数とkeep-aliveになります。SpringがSchedulingとして使用するスレッドはデフォルトだと1なので、スケジュールを複数設置すると同時実行は1つのみですので、実際に使う場合は設定を変更しておくと良いと思います。spring.main.keep-alive は、バーチャルスレッドを有効にしている場合に、バーチャルスレッドは動作しているが、メインスレッドの処理が止まるとJVMが終了してしまうため、それを回避するための設定です。有効にしておくと不慮のJVM終了にはならないと思います。

テーブル定義

init.sqlは下記のようにします。プロダクション環境ではFlyway等のマイグレーションツールで設定してください。

CREATE TABLE shedlock  
(  
    name       VARCHAR(64)  NOT NULL,  
    lock_until TIMESTAMP    NOT NULL,  
    locked_at  TIMESTAMP    NOT NULL,  
    locked_by  VARCHAR(255) NOT NULL,  
    PRIMARY KEY (name)  
);

アプリケーションコード

package net.ksasaki.sample.schedule;  
  
import lombok.extern.slf4j.Slf4j;  
import net.javacrumbs.shedlock.core.LockProvider;  
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;  
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;  
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.jdbc.core.JdbcTemplate;  
import org.springframework.scheduling.annotation.EnableScheduling;  
import org.springframework.scheduling.annotation.Scheduled;  
import org.springframework.stereotype.Component;  
  
import javax.sql.DataSource;  
import java.time.LocalDateTime;  
  
@SpringBootApplication  
public class ScheduleApplication {  
  
    public static void main(String[] args) {  
       SpringApplication.run(ScheduleApplication.class, args);  
    }  
  
}  
  
  
@EnableScheduling  
@EnableSchedulerLock(defaultLockAtMostFor = "10m")  
@Configuration
class ScheduleConfig {   // ①
    @Bean  
    public LockProvider lockProvider(DataSource dataSource) {  
       return new JdbcTemplateLockProvider(  
             JdbcTemplateLockProvider.Configuration.builder()  
                   .withJdbcTemplate(new JdbcTemplate(dataSource))  
                   .withLockedByValue( "schedule")  
                   .usingDbTime()  
                   .build()  
       );  
    }  
}  
  
@Component  
@Slf4j  
class ScheduleComponent {  // ②
  
    @Scheduled(fixedRate = 1000, initialDelay = 1000)  // ③
    @SchedulerLock(name = "scheduleComponentA", lockAtLeastFor = "3s")  // ④
    public void runA() throws InterruptedException {  
       log.info("Scheduled task is running... A {}", LocalDateTime.now());  
    }  
  
    @Scheduled(fixedRate = 1000, initialDelay = 1000)  
    @SchedulerLock(name = "scheduleComponentB" , lockAtLeastFor = "4s")  
    public void runB() {  
       log.info("Scheduled task is running... B {}", LocalDateTime.now());  
    }  
  
    @Scheduled(fixedRate = 500, initialDelay = 1000)  
    @SchedulerLock(name = "scheduleComponentC" , lockAtLeastFor = "5s")  
    public void runC() {  
       log.info("Scheduled task is running... C {}", LocalDateTime.now());  
    }  
  
    @Scheduled(fixedRate = 1000, initialDelay = 1000)  
    @SchedulerLock(name = "scheduleComponentD", lockAtLeastFor = "2s")  
    public void run0() throws InterruptedException {  
       log.info("Scheduled task is running... D {}", LocalDateTime.now());  
    }  
}

1ファイルにしたので、長いですけど順を追って解説します。

ShedLockの設定

// ①の部分になりますが、LockProviderの設定が必要になります。基本的にはSpringのDataSourceオブジェクトを渡すくらいで処理が完了します。

@EnableScheduling // ①
@EnableSchedulerLock(defaultLockAtMostFor = "10m") // ②
@Configuration
class ScheduleConfig {
    @Bean  
    public LockProvider lockProvider(DataSource dataSource) {  
       return new JdbcTemplateLockProvider(  
             JdbcTemplateLockProvider.Configuration.builder()  
                   .withJdbcTemplate(new JdbcTemplate(dataSource))  
                   .withLockedByValue( "schedule")  // locked_byカラムのデータ
                   .usingDbTime()  
                   .build()  
       );  
    }  
}  

① スケジュール機能を有効にする

@EnableSchedulingでスケジュール機能を有効にします。

② ShedLock機能を有効にする

@EnableSchedulerLock(defaultLockAtMostFor = "10m")ShedLockを有効にします。defaultLockAtMostForは、デフォルトの最長ロック時間を指定します。

スケジュール設定部分

SpringBootを起動する部分は省いてスケジュール設定をする部分のみ説明します。

@Component
@Slf4j  
class ScheduleComponent {  // ③
  
    @Scheduled(fixedRate = 1000, initialDelay = 1000)  // ④
    @SchedulerLock(name = "scheduleComponentA", lockAtLeastFor = "3s")  // ⑤
    public void runA() throws InterruptedException {  
       log.info("Scheduled task is running... A {}", LocalDateTime.now());  
    }  
  
    @Scheduled(fixedRate = 1000, initialDelay = 1000)  
    @SchedulerLock(name = "scheduleComponentB" , lockAtLeastFor = "4s")  
    public void runB() {  
       log.info("Scheduled task is running... B {}", LocalDateTime.now());  
    }  
  
    @Scheduled(fixedRate = 500, initialDelay = 1000)  
    @SchedulerLock(name = "scheduleComponentC" , lockAtLeastFor = "5s")  
    public void runC() {  
       log.info("Scheduled task is running... C {}", LocalDateTime.now());  
    }  
  
    @Scheduled(fixedRate = 1000, initialDelay = 1000)  
    @SchedulerLock(name = "scheduleComponentD", lockAtLeastFor = "2s")  
    public void run0() throws InterruptedException {  
       log.info("Scheduled task is running... D {}", LocalDateTime.now());  
    }  
}

③ スケジュールクラスのDI設定

スケジュール設定をするには、DIに登録する必要がありますので、classに@Component、``を付与します。

④ スケジュールの設定

Springのスケジューリングの設定はアノテーションで設定を行う。fixedRate完了後、次の実行時までの時間をms単位で設定します。initialDelayは、SpringBoot起動後、初回の開始時間までの時間をms単位で設定します。

@Scheduled(fixedRate = 1000, initialDelay = 1000)  // ③

⑤ ShedLockの設定

ShedLock の設定を行います。

@SchedulerLock(name = "scheduleComponentA", lockAtLeastFor = "3s")  // ④
name: ロックする名前
lockAtLeastFor: 最小時間
lockAtMostFor: 最大時間

以上となります。nameはこのあと紹介するDBのデータでPKとなっています。lockAtLeastForは、タスクが短時間で完了してもロックし続ける時間になります。lockAtMostForは、ロックしつづける最大時間になります。

起動後

ログ

ログは下記のようになります。わかりにくいですが、ロックされている時間は動作せずにスキップされています。複数台で実行すると、片方のインスタンスのみで実行されているのがわかります。

テーブルの中身

テーブルの中身は下記のようになっています。ロックされて、それぞれ同時実行できないようになっています。@Scheduledの実行タイミングで、チェックにしてロックされていたらスキップするような挙動になっています。

まとめ

バッチ処理は複数台で同時実行を制御するのが一番面倒かなとおもいます。ShedLockはその部分のみを担ってくれます。SpringBatchにあるような分散処理系で便利な、クラッシュ時のリスタート、チェックポイント、スキップやチャンク指向処理など機能はありません。バッチ処理をSpringBatchでオンラインアプリケーションコードとバッチ処理のコードの乖離が進む傾向にあると思います。なるべくビジネスロジックは、オンラインアプリケーションコードに寄せた形でのバッチ処理を書くときに、ShedLockを使う選択肢はありだと思います。ぜひ試してみてください。

(参加レポート) 設計ナイト2025【オフライン】

吉祥寺開催の設計ナイトに初めて参加してきました。結構高度で刺激的な内容でした。間違っているところもあると思いますが、

kichijojipm.connpass.com

誰のための設計?

https://www.docswell.com/s/tyonekubo/58MQP2-2025-07-25-sekkeinight#p1

人間中心設計の話から入り、デザインの原理、そしてプログラム設計の話にはいっていきます。手続き型コード(ifとかforとか)を多用するコードは、高い認知負荷バグの温床低い可読性密結合による拡張性の欠如に繋がるとしています。印象に残ったのは、解決方法にはDSLがよいみたいな話があり、AI時代はDSLの構築コストが下がっているから導入してもいいんじゃないか?というような提起でした。個人的にはDSLの構築コストが下がったとしても読み解くコストは増える、または読み解くのにAIなしだとキツイみたいな感じになるなら、無理にDSLにせずに型設計をちゃんとして解決していった方が難易度は上がらずに継続的な感じになるんじゃないかと思いました。

なぜAI時代に 「イベント」を中心に考えるのか?

https://speakerdeck.com/ytake/why-focus-on-events-in-the-age-of-ai

データが大事、時間軸のデータが大事という話でAI時代はイベントソーシングのようなデータがより重要になるという話でした。これには同意で、AI時代に限らずデータはとても大切でライフサイクルも長いです。ここ十数年テーブル設計、データ設計に注力してある程度の解は自分の中で確立しています。AI時代だから・・・っていうのは違うと思いますけど、データが大事というのはとても重要です。

ecspressoの設計思想に至る道 / sekkeinight2025

https://speakerdeck.com/fujiwara3/sekkeinight2025

ecspressoの設計思想に関する話でした。結論としては作成開始当初は設計思想というものはなかったけど、気付いたらそういう設計思想だったみたいな話です。これはFUJIWARAさんの頭の中にある経験や設計思想がプロダクトにモロに表れてて、私もそれに強く共感できたので楽しめました。簡単に言うと設計するにあたり境界が大事ということでした。如何に依存を断ち切れるかは、設計思想では大事なことだとおもいます。

可変性を制する設計: 構造と振る舞いから考える概念モデリングとその実装

自分には少しむずかしくて、うまくまとめられませんでした。すみません。

ADR運用におけるデータ利活用の考え方

ADRをデータ -> 情報 -> 知識 -> 知恵にしていこうという話でした。データだけ集めて情報にして、知識さらには知恵にしていくのが大事という話です。現時点ではアーキテクチャの意思決定時にドキュメントも含め残していますが、これは実践してみないとですので、今後やっていこうとおもいます。

AIエージェント開発を支える設計

AIエージェントを作っていくにあたり、

どうかと思う設計選手権LT

https://docs.google.com/presentation/d/138lZji5oJ-W7zM-I8N3gYB6rzucwFONw7IXbmTSCBkA/edit

ずっと大きく頷いていた話でした。かなり大雑把にいうと、オーバーエンジニアリングは良くないという話です。SOLID過剰適用もベストプラクティス偏重、モダン至上主義、関数型プログラミングの型の使いすぎ、エフェクト過剰みたいなものは、最終的に認知負荷が増大し、崩壊につながるかもみたいなものは多くみてきましたので、オーバーエンジニアリングなのかどうかを常々チェックできるようにこれからも精進していきたいと思います。

書籍紹介

ソフトウェア設計のトレードオフと誤り ―プログラミングの際により良い選択をするには

ソフトウェア設計のトレードオフと誤り ―プログラミングの際により良い選択をするには

この書籍は、設計によるトレードオフと誤りをまとめてくれている本です。なんでもそうですけど、銀の弾丸はないのでどんなに良い技術でも絶対に、やりすぎ、偏重、オーバーエンジニアリングをすると運用コストがあがりや変更容易性が下がるといった影響がでてきます。技術でも設計でも必ずトレードオフがあることを教えてくれる良い書籍だと思います。

まとめ

はじめて設計ナイトに参加させていただきたいが、登壇している皆様の試行錯誤の話が聞けてよかったです。プロダクトやメンバーの状況により採用する設計は異なると思いますので、自分も精進していければとおもいます。また、次回参加させていただきたいとおもいます。

設計ナイトの宣伝(おまけ)

近々のイベントの紹介があったのでわかる範囲ないで貼っておきます。

大吉祥寺.pm https://kichijojipm.connpass.com/event/361631/

キチピー リジェクトコン【非公式】 https://connpass.com/event/363351/

第十二回技術書同人誌博覧会 https://gishohaku.connpass.com/event/352601/

ShizuokaTECH#1 https://shizuoka-tech.connpass.com/event/350174/

OCIJP https://fullenergy-oci.connpass.com/event/362399/