クリーンコード【境界編】

はじめに

本記事では、Robert C. Martinの名著『Clean Code』の第8章「境界」に関して、自分が大切だと感じた箇所を、具体的なコードを追加しながらまとめました。本書にはさらに詳細なベストプラクティスが含まれていますので、興味がある方はぜひお読みください。

イントロダクション

この記事では、外部のコードと自分のコードを綺麗に接続するための方法を見ていきます。

サードパーティのインターフェース境界

サードパーティのパッケージやフレームワークは、多様なアプリケーションで動作することが求められますが、使用者側のニーズに特化したインターフェースを提供するわけではありません。例えば、java.util.Mapを利用する場合を考えてみましょう。

Map<Member> members = new HashMap<>();
...
Member m = members.get(memberId);

Mapにはget以外にも多くの機能が含まれているため、アプリケーションにとって本来必要以上の機能を提供してしまいます。さらに、Mapインターフェースをシステム内で無造作に使用すると、Map自体が変更された場合に多くの箇所で影響を受けることになります。Mapのような標準ライブラリであれば頻繁に変更されることは少ないかもしれませんが、サードパーティのライブラリではバージョンアップによる変更が頻繁に発生します。加えて、別のライブラリに乗り換える際には修正が大変になります。

こうした問題を回避するために、以下のようにラップすることでMapとのインターフェースを隠すことができます。

public class Members {
    private Map members = new HashMap<>();

    public Member getById(String id) {
        return members.get(id);
    }
}

この方法により、アプリケーションの他の部分への影響を最小限に抑え、拡張や変更も容易になります。また、アプリケーションのニーズに合った設計とビジネスルールを強制することができ、コードの理解が容易になり、誤用も防ぎやすくなります。

ただし、必ずしもMapを利用する際にこのようにカプセル化すべきというわけではありません。重要なのは、サードパーティのインターフェースを使用する際は、クラス内部や強い関連を持ったクラス内だけでの使用に留め、システム全体で持ち回らないようにするということです。また、公開APIでこれらを返したり、引数として受け取ることは避けるべきです。

サードパーティの学習テスト

サードパーティの学習テストを書くことで、対象の知識を的確に実験することができますし、新バージョンのリリース時に互換性のない変更が行われた際にも直ちに発見できます。また、テストによって外部との明確な境界を担保し、将来の移行を簡単に行うことができます。以下は、log4jに対する学習テストの例です。

public class LogTest {
    private Logger logger;

    @Before
    public void initialize() {
        logger = Logger.getLogger("logger");
        logger.removeAllAppenders();
        Logger.getRootLogger().removeAllAppenders();
    }

    @Test
    public void basicLogger() {
        BasicConfigurator.configure();
        logger.info("basicLogger");
    }

    @Test
    public void addAppenderWithStream() {
        logger.addAppender(new ConsoleAppender(
            new PatternLayout("%p %t %m%n"),
            ConsoleAppender.SYSTEM_OUT));
        logger.info("addAppenderWithStream");
    }

    @Test
    public void addAppenderWithoutStream() {
        logger.addAppender(new ConsoleAppender(
            new PatternLayout("%p %t %m%n")));
        logger.info("addAppenderWithoutStream");
    }
}

未知のコードに対する境界

未知の部分があるシステム開発において、我々に都合のいいインターフェースを先に定義し、アダプターパターンを用いて後からその部分の違いを吸収するというアプローチは、非常に有効な手法です。

例えば、Eコマースプラットフォームの開発プロジェクトで、支払いシステムを導入する際にこの手法を適用するとします。支払いシステムの詳細なAPI仕様はまだ確定していません。まず、我々のシステムに必要なインターフェースを定義します。

public interface PaymentService {
    void processPayment(PaymentDetails paymentDetails);
    PaymentStatus getPaymentStatus(String transactionId);
}

このインターフェースを用いて実装を進めることができます。

public class PaymentController {

    private PaymentService paymentService;

    public PaymentController(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void handlePayment(PaymentDetails paymentDetails) {
        paymentService.processPayment(paymentDetails);
    }

    public PaymentStatus checkPaymentStatus(String transactionId) {
        return paymentService.getPaymentStatus(transactionId);
    }
}

実際の支払いシステム(例えば、Stripe)のAPI仕様が確定したら、アダプターを作成してインターフェースに適合させます。

public class PaymentServiceAdapter implements PaymentService {

    private StripeApi stripeApi;

    public PaymentServiceAdapter() {
        this.stripeApi = new StripeApi(); // Stripe APIの初期化
    }

    @Override
    public void processPayment(PaymentDetails paymentDetails) {
        // Stripe APIを使った支払い処理の実装
        stripeApi.charge(paymentDetails.getAmount(), paymentDetails.getCurrency(), paymentDetails.getCardDetails());
    }

    @Override
    public PaymentStatus getPaymentStatus(String transactionId) {
        // Stripe APIを使った支払いステータスの取得
        return stripeApi.retrievePaymentStatus(transactionId);
    }
}

このアプローチにより、次のような利点が得られます。

  • 柔軟性: 支払いシステムの詳細が不明な間でも、既存のシステムに影響を与えずに他の部分の開発を進めることができます。
  • モジュール化: インターフェースを介して支払いシステムを切り替えることが容易になります。
  • テスト容易性: 各支払いシステムの実装を独立してテストすることが可能です。

このように、インターフェースを先に定義し、アダプターパターンを利用することで、システム開発の柔軟性と効率性を高めることができます。

結論

サードパーティのコードに依存する際には、直接使用するのではなく、自分たちが制御できる小さな領域にラップするか、アダプターパターンを使用して接続することが重要です。これにより、サードパーティのコードが変更されても、影響を最小限に抑えることができます。

例えば、java.util.Mapを直接使用するのではなく、独自のクラスでラップし、そのクラスを通じてMapにアクセスするようにすることで、変更の影響を最小限に抑えることができます。また、未知の外部システムとのインターフェースを先に定義し、後からアダプターを実装することで、柔軟性とテスト容易性が向上します。自分たちのコードの広範囲にサードパーティのコードの詳細を知らしめることを避け、自分たちが制御できるものに依存することが最善です。

関連記事

カテゴリー:

ブログ

情シス求人

  1. チームメンバーで作字やってみた#1

ページ上部へ戻る