IT求人市場2026年:開発者が投資すべきスキル

1. 概要

IT求人市場は各開発者にとってより多くを要求する方向に変わっています。機能を書くだけでは十分ではありません。本番環境でその機能がどのように動作し、失敗し、どのくらいのコストがかかるかを理解する必要があります。

この記事では数字や給与予測は提示しません。代わりに、現在注目されている5つの基礎スキルグループを分析し、その理由を説明し、Java 21/Spring Boot 3.xのコードで各グループを示します。読み終わったら、何を優先的に学ぶべきか、プロジェクトにすぐに適用する方法、そしていつトレンドに従わないべきかを知ることができます。

2. なぜスキル要件が変わっているのか

3つの主な推進力があり、ほとんどの求人情報で自分で観察できます:

  • AIによるコード生成ツールが普及しました。 「ボイラープレート」コードはより速く生成されます。開発者の価値は、設計、レビュー、テスト、そして結果への責任に移行しています。
  • 企業は運用コストに注力しています。 リソースをより少なく使用し、エラー診断が容易なサービスは、直接的にコストを節約します。
  • チームが小さく、所有権が広くなっています。 「自分たちで作り、自分たちで運用する」モデルにより、開発者はパッケージング、監視、トラブルシューティングを知る必要があります。

その結果、以下の5つのスキルグループが長期的なリターンを持つ投資として浮上しています。

3. 制御されたAI統合

現在最も頻繁に言及されるスキルは「チャットボットの使い方を知る」ことではなく、実際のシステムにLLMを統合する能力であり、それでも安定性、テスト可能性、フォールバック計画を維持することです。

コアの原則:LLMを信頼できない外部依存関係として扱います。その出力はフォーマットが間違っている可能性があり、遅い可能性があり、またはネットワークエラーである可能性があります。

3.1. インターフェースの後ろにLLMを分離する

@FunctionalInterface
public interface LlmClient {
    String complete(String prompt);
}

public enum TicketCategory { BILLING, TECHNICAL, OTHER }

public class TicketClassifier {

    private final LlmClient llm;

    public TicketClassifier(LlmClient llm) {
        this.llm = llm;
    }

    public TicketCategory classify(String message) {
        String prompt = """
            以下のサポートリクエストを適切なラベルに分類してください:BILLING、TECHNICAL、OTHER。
            ラベルのみを返し、説明は不要です。
            リクエスト:%s
            """.formatted(message);
        try {
            String raw = llm.complete(prompt).trim().toUpperCase();
            return TicketCategory.valueOf(raw);
        } catch (RuntimeException e) {
            return TicketCategory.OTHER;
        }
    }
}

説明:

  • LlmClientはあなた独自のインターフェースです。実装はHTTP経由または Spring AIのようなライブラリを介して任意のプロバイダーを呼び出すことができます。ビジネスロジックは特定のプロバイダーに依存しません。
  • プロンプトは出力が単一のラベルであることを要求します。フォーマット制約により、結果を解析できます。
  • TicketCategory.valueOf(raw)は解析と検証を同時に行います。モデルが長い文を返す場合、IllegalArgumentExceptionがキャッチされ、システムはOTHERにフォールバックします。ネットワークエラー(通常はRuntimeException)もこのブランチに入ります。

3.2. 実際のモデルを呼ぶことなくテストする

LlmClientは関数型インターフェースなので、ラムダをフェイクとして渡すことができます:

import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;

class TicketClassifierTest {

    @Test
    void givenValidLabel_whenClassify_thenReturnCategory() {
        TicketClassifier classifier = new TicketClassifier(prompt -> " billing\n");

        assertThat(classifier.classify("同じ金額で2回引かれました"))
            .isEqualTo(TicketCategory.BILLING);
    }

    @Test
    void givenUnexpectedOutput_whenClassify_thenFallbackToOther() {
        TicketClassifier classifier = new TicketClassifier(prompt -> "おそらくBILLINGです");

        assertThat(classifier.classify("同じ金額で2回引かれました"))
            .isEqualTo(TicketCategory.OTHER);
    }

    @Test
    void givenClientFailure_whenClassify_thenFallbackToOther() {
        TicketClassifier classifier = new TicketClassifier(prompt -> {
            throw new IllegalStateException("timeout");
        });

        assertThat(classifier.classify("アプリがクラッシュしました")).isEqualTo(TicketCategory.OTHER);
    }
}

最初のテストは、コードが空白と小文字に対応していることを示しています。その後の2つのテストは、モデルが「多くを語る」場合またはネットワークエラーの場合、システムが崩壊しないことを証明しています。これは採用担当者が見たいものです:LLMが失敗することを知っていて、そのケースのために設計していることです。

注意: ルール、正規表現、またはSQLクエリで正確かつより安く実行できるタスクにはLLMを使用しないでください。また、確定的で監査可能な結果が必要な決定(請求や権限付与など)にはLLMを使用しないでください。

4. Virtual Threadsを使った現代的な並行処理

ほとんどのバックエンドはI/O境界です:データベースを呼び出し、他のAPIを呼び出します。Java 21はvirtual threadsを公式な機能にし、親しみのあるブロッキングコードを書きながら、多くの同時リクエストに対応できます。いつ使うべきか、いつ使うべきでないかを理解することは、インタビューで明らかな利点です。

import java.time.Duration;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class PriceAggregator {

    public static void main(String[] args) throws Exception {
        List<String> suppliers = List.of("SupplierA", "SupplierB", "SupplierC");
        long start = System.currentTimeMillis();

        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            List<Future<String>> futures = suppliers.stream()
                .map(s -> executor.submit(() -> fetchPrice(s)))
                .toList();
            for (Future<String> f : futures) {
                System.out.println(f.get());
            }
        }
        System.out.println("合計時間: " + (System.currentTimeMillis() - start) + " ms");
    }

    static String fetchPrice(String supplier) throws InterruptedException {
        Thread.sleep(Duration.ofSeconds(1)); // 遅いAPI呼び出しをシミュレート
        return supplier + " -> 100000 VND, virtual=" + Thread.currentThread().isVirtual();
    }
}
  • newVirtualThreadPerTaskExecutor()は各タスクに対して仮想スレッドを作成します。プールサイズを微調整する必要はありません。
  • try-with-resourcesブロックは、エグゼキュータがすべてのタスクが完了するのを待ってから閉じることを保証します。
  • 1秒遅い3つの呼び出しは並列に実行され、合計時間は約1秒です:
SupplierA -> 100000 VND, virtual=true
SupplierB -> 100000 VND, virtual=true
SupplierC -> 100000 VND, virtual=true
合計時間: 1015 ms

Spring Boot 3.2以降では、1行でリクエスト処理のvirtual threadsを有効にできます:

spring.threads.virtual.enabled=true

注意: Virtual threadsはCPU境界のコードを高速化しません。長いI/O操作を囲むsynchronizedブロックに注意してください。仮想スレッドをキャリアスレッドに固定する可能性があります。影響の程度はJDKバージョンに依存します。また、より多くのタスクを同時に実行するとデータベースコネクションプールが枯渇する可能性があります。バックエンドでリソースを制限する必要があります。

5. Observability:システムが何をしているかを知る

運用の責任を負う場合、「なぜ遅いのか?」という質問はデータで答える必要があります。メトリクス、ログ、トレースの3つの柱です。Springエコシステムでは、MicrometerとActuatorが自然な出発点です。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

カスタムビジネスメトリクスを追加する:

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    private final Counter placedOrders;

    public OrderService(MeterRegistry registry) {
        this.placedOrders = Counter.builder("orders.placed")
            .description("正常に注文された注文数")
            .tag("channel", "web")
            .register(registry);
    }

    public void placeOrder(String orderId) {
        // ... 注文を保存し、PaymentServiceを呼び出す ...
        placedOrders.increment();
    }
}
management.endpoints.web.exposure.include=health,metrics
  • MeterRegistryはSpring Bootによって自動構成され、ビーンにインジェクトされます。
  • タグchannelを使用すると、ダッシュボードを表示するときに販売チャネルごとにメトリクスを分離できます。
  • 設定行は2つのエンドポイントhealthとmetricsのみを開き、すべてを開きません。

いくつかの注文の後、GET /actuator/metrics/orders.placedを呼び出します:

{
  "name": "orders.placed",
  "description": "正常に注文された注文数",
  "measurements": [ { "statistic": "COUNT", "value": 3.0 } ],
  "availableTags": [ { "tag": "channel", "values": [ "web" ] } ]
}

注意: 高いカーディナリティと無制限の値(orderIdやcustomerIdなど)をタグとして使用しないでください。各タグ値は新しい時系列を作成し、メトリクスシステムが非常に速く膨張します。また、すべてのアクチュエータエンドポイントをインターネットに公開しないでください。

6. Testcontainersを使った実インフラストラクチャでのテスト

AIがコードを速く生成できる場合、テストがコードが正しいことを証明するものです。実際のデータベース上で実行される統合テスト(別の言語のインメモリデータベースではなく)は、モックが捕捉できないエラーを捕捉するのに役立ちます。

次の依存関係はSpring Bootによってバージョン管理されます:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-testcontainers</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <scope>test</scope>
</dependency>

最小限のエンティティとリポジトリ:

@Entity
public class Customer {
    @Id @GeneratedValue
    private Long id;
    private String email;

    protected Customer() {}
    public Customer(String email) { this.email = email; }
    public Long getId() { return id; }
    public String getEmail() { return email; }
}

public interface CustomerRepository extends JpaRepository<Customer, Long> {
    Optional<Customer> findByEmail(String email);
}

統合テスト:

@SpringBootTest
@Testcontainers
class CustomerRepositoryIT {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");

    @Autowired
    CustomerRepository repository;

    @Test
    void givenSavedCustomer_whenFindByEmail_thenReturnCustomer() {
        repository.save(new Customer("lan@example.com"));

        assertThat(repository.findByEmail("lan@example.com"))
            .isPresent()
            .get()
            .extracting(Customer::getEmail)
            .isEqualTo("lan@example.com");
    }
}
  • @Testcontainersと@Containerは、テストが実行される前にDockerで実際のPostgreSQLを起動します。
  • @ServiceConnection(Spring Boot 3.1以降)は、コンテナのURL、ユーザー名、パスワードをDataSourceに自動的に追加します。@DynamicPropertySourceは必要ありません。
  • クエリfindByEmailはSpring Dataによって生成され、本番環境のまったく同じタイプのデータベース上で実行されます。

注意: Testcontainersには、開発マシンとCIでDockerが必要です。テストはユニットテストより遅くなります。ロジックのほとんどをピュアユニットテストに保ち、Testcontainersはリポジトリ、メッセージング、マイグレーションなどの統合層に使用するだけです。

7. コンテナのパッケージングと運用

アプリケーションがどのようにパッケージ化され実行されるかを理解することは、Kubernetesやクラウドプラットフォームで作業するときの最小限のスキルです。マルチステージDockerfileはイメージサイズとセキュリティに関心があることを示しています:

FROM eclipse-temurin:21-jdk AS build
WORKDIR /app
COPY . .
RUN ./mvnw -q package -DskipTests

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
USER 1000
ENTRYPOINT ["java", "-jar", "app.jar"]
  • ビルドステージはコンパイル用のJDKとMaven wrapperを持っています。
  • 最後のステージはJREとjarファイルのみを含むため、イメージは小さく、攻撃面も少なくなります。
  • USER 1000は、コンテナがrootで実行されないようにします。これは非常に一般的なエラーです。

セクション5の/actuator/healthエンドポイントを組み合わせて、デプロイ時のliveness/readiness probeとして使用できます。

注意: チームがBuildpacks(たとえば./mvnw spring-boot:build-image経由)を既に使用している場合、Dockerfileを自分で書く必要はありません。重要なのは、イメージに何が含まれているか、どのユーザーで実行されるか、メモリ制限がJVMにどのように影響するかを理解することです。

8. トレンドに従うべきでない時期

スキルへの投資も冷静である必要があります:

  • 基本を忘れないでください。 データ構造、SQL、HTTP、トランザクション、エラー処理は、すべてのインタビューで質問されるものです。AIとフレームワークはこれらの知識よりもはるかに速く変わります。
  • あちこちを学ばないでください。 現在の仕事に関連する1つか2つのスキルを選択し、実際のプロジェクトに適用してから、拡大してください。メトリクスと統合テストを追加するPRは、使用されていない10個の証明書よりも価値があります。
  • CVにあるためだけに技術を導入しないでください。 小さなシステムやユーザーが少ないシステムはKubernetes、マイクロサービス、またはLLMを必要としません。理由を添えて「必要ない」と言うことができるのは、高く評価されるスキルでもあります。
  • コード生成ツールに完全に委ねないでください。 あなたはまだ責任者です。生成されたすべてのコードを読み、理解し、テストを書いてください。

9. 結論

IT市場は、機能のライフサイクル全体をマスターする開発者に報酬を与えています:設計、コード記述、テスト、パッケージング、運用。この記事は5つの投資価値のあるスキルグループを説明しました:

  • インターフェースの背後にあるLLM統合(検証とフォールバック付き)。
  • I/O境界ワークロード用のvirtual threads。
  • MicrometerとActuatorによるobservabilityの追加。
  • Testcontainersを使った実インフラストラクチャ上での統合テストの記述。
  • セキュアなコンテナのパッケージング。

各スキルには独自の制限があり、その制限を知ることは使い方を知ることと同じくらい重要です。1つのセクションを選択し、今週行っているサービスにすぐに適用してください。これはトレンドを実際の経験に変える最速の方法です。

キャリアtech-trendsjavaspring-bootai