技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

ソースコード修正の競合と統合

複数の案件が同時に動いており、1つのソースコードを複数の案件で修正する場合、ソースコード修正の競合(コンフリクト)の統合(マージ、差分取り込み)が必要になります。
統合には、全体のロジックの整合性や案件のリリース時期の違いを考慮する必要があり、機械的に判断できない場合もあるので、最終的には人の手で判断し統合する必要があります。
 
----------------
 
例えば、商品の販売価格を計算する以下のソースコードがあるとします。
(今回はjavaソースコードであると仮定します)
 
・修正前ソース
public class SalesPriceCalc {

  // DBサクセサのオブジェクト
  private DBAccessor dBAccessor;
  
  // 消費税
  private final TAX_RATE = 1.05;
  
  public SalePriceCalc() {
    dBAccessor = new DBAccessor();
  }

  public int execute(int commodityId) {

    int salePrice;
    
    // 商品の値段の取得
    salesPrice = dBAccessor.getCommodityPrice(commodityId);
    
    // 最終的な販売価格を返す
    return (int) Math.floor(salePrice * TAX_RATE);
  }
}
 
このソースコードについて、下記3案件で修正するものとします。
案件1.消費税増税対応(3ヶ月後リリース)
案件2.店舗毎セール対応(3ヶ月後リリース)
案件3.タイムセール対応(6ヶ月後リリース)
 
3案件の修正後ソースはそれぞれ以下の通りとします。
 
・案件1
public class SalesPriceCalc {

  // DBサクセサのオブジェクト
  private DBAccessor dBAccessor;
  
  // 消費税
  private final TAX_RATE = 1.08; // 修正ポイント1
  
  public SalePriceCalc() {
    dBAccessor = new DBAccessor();
  }

  public int execute(int commodityId) {

    int salePrice;
    
    // 商品の値段の取得
    salesPrice = dBAccessor.getCommodityPrice(commodityId);
    
    // 最終的な販売価格を返す
    return (int) Math.floor(salePrice * TAX_RATE);
  }
}
 
・案件2
public class SalesPriceCalc {

  // DBサクセサのオブジェクト
  private DBAccessor dBAccessor;
  
  // 店舗毎割引率取得オブジェクト // 修正ポイント2-1
  private ShopPriceRateCalc shopPriceRateCalc;
  
  // 消費税
  private final double TAX_RATE = 1.05;
  
  public SalePriceCalc() {
    dBAccessor = new DBAccessor();
    shopPriceRateCalc = new ShopPriceRateCalc(); // 修正ポイント2-2
  }
  
  public SalePriceCalc(int shopId) { // 修正ポイント2-3
    dBAccessor = new DBAccessor();
    shopPriceRateCalc = new ShopPriceRateCalc(shopId);
  }

  public int execute(int commodityId) {

    int salePrice;
    double shopPriceRate;
    
    // 商品の値段の取得
    salesPrice = dBAccessor.getCommodityPrice(commodityId);
    
    // 店舗毎割引率の取得 // 修正ポイント2-4
    shopPriceRate = shopPriceRateCalc.getShopPriceRate(commodityId);
    
    // 最終的な販売価格を返す // 修正ポイント2-5
    return (int) Math.floor(salePrice * TAX_RATE * shopPriceRate);
  }
}
 
・案件3
import java.util.Date;

public class SalesPriceCalc {

  // DBサクセサのオブジェクト
  private DBAccessor dBAccessor;
  
  // タイムセール割引率取得オブジェクト // 修正ポイント3-1
  private TimeSaleRateCalc timeSaleRateCalc;
  
  // 消費税
  private final TAX_RATE = 1.05;
  
  public SalePriceCalc() {
    dBAccessor = new DBAccessor();
    timeSaleRateCalc = new TimeSaleRateCalc(); // 修正ポイント3-2
  }

  public int execute(int commodityId) {

    int salePrice;
    double timeSaleRate;
    
    // 商品の値段の取得
    salesPrice = dBAccessor.getCommodityPrice(commodityId);
    
    // タイムセール割引率の取得 // 修正ポイント3-3
    Date systemDateTime = new Date();
    timeSaleRate
      = timeSaleRateCalc.getTimeSaleRate(commodityId, systemDateTime);
    
    // 最終的な販売価格を返す // 修正ポイント3-4
    return (int) Math.floor(salePrice * TAX_RATE * timeSaleRate);
  }
}
 
リリースすることを考えると、以下の2段階に分けてソースコードの修正を統合する必要があります。
・3ヶ月後を想定
 案件1と案件2を統合
・6ヶ月後を想定
 3ヶ月後のソースと案件3を統合
 
案件1と案件2の統合は容易です。
以下のように機械的に統合することができます。
 
・3ヶ月後のソース
public class SalesPriceCalc {

  // DBサクセサのオブジェクト
  private DBAccessor dBAccessor;
  
  // 店舗毎割引率取得オブジェクト // 修正ポイント2-1
  private ShopPriceRateCalc shopPriceRateCalc;
  
  // 消費税
  private final double TAX_RATE = 1.08; // 修正ポイント1
  
  public SalePriceCalc() {
    dBAccessor = new DBAccessor();
    shopPriceRateCalc = new ShopPriceRateCalc(); // 修正ポイント2-2
  }
  
  public SalePriceCalc(int shopId) { // 修正ポイント2-3
    dBAccessor = new DBAccessor();
    shopPriceRateCalc = new ShopPriceRateCalc(shopId);
  }

  public int execute(int commodityId) {

    int salePrice;
    double shopPriceRate;
    
    // 商品の値段の取得
    salesPrice = dBAccessor.getCommodityPrice(commodityId);
    
    // 店舗毎割引率の取得 // 修正ポイント2-4
    shopPriceRate = shopPriceRateCalc.getShopPriceRate(commodityId);
    
    // 最終的な販売価格を返す // 修正ポイント2-5
    return (int) Math.floor(salePrice * TAX_RATE * shopPriceRate);
  }
}
 
対して、3ヶ月後のソースと案件3を統合は機械的に行うことができません。
修正ポイント2-5と修正ポイント3-4は同じ個所を修正しているので、両方の修正が反映されるようにソースを書き変えなければなりません。
そもそも、修正ポイント2-3でコンストラクタが追加になっているため、新たに追加したコンストラクタに対しても案件3の内容を反映しなければなりません。
よって、6ヶ月後のソースは以下のようになります。
 
・6ヶ月後のソース
public class SalesPriceCalc {

  // DBサクセサのオブジェクト
  private DBAccessor dBAccessor;
  
  // 店舗毎割引率取得オブジェクト // 修正ポイント2-1
  private ShopPriceRateCalc shopPriceRateCalc;
  
  // タイムセール割引率取得オブジェクト // 修正ポイント3-1
  private TimeSaleRateCalc timeSaleRateCalc;
  
  // 消費税
  private final double TAX_RATE = 1.08; // 修正ポイント1
  
  public SalePriceCalc() {
    dBAccessor = new DBAccessor();
    shopPriceRateCalc = new ShopPriceRateCalc(); // 修正ポイント2-2
    timeSaleRateCalc = new TimeSaleRateCalc(); // 修正ポイント3-2
  }
  
  public SalePriceCalc(int shopId) { // 修正ポイント2-3+案件3の統合
    dBAccessor = new DBAccessor();
    shopPriceRateCalc = new ShopPriceRateCalc(shopId);
    timeSaleRateCalc = new TimeSaleRateCalc();
  }

  public int execute(int commodityId) {

    int salePrice;
    double shopPriceRate;
    
    // 商品の値段の取得
    salesPrice = dBAccessor.getCommodityPrice(commodityId);
    
    // 店舗毎割引率の取得 // 修正ポイント2-4
    shopPriceRate = shopPriceRateCalc.getShopPriceRate(commodityId);
    
    // タイムセール割引率の取得 // 修正ポイント3-3
    Date systemDateTime = new Date();
    timeSaleRate
      = timeSaleRateCalc.getTimeSaleRate(commodityId, systemDateTime);

    // 最終的な販売価格を返す // 修正ポイント2-5+修正ポイント3-4
    return (int) Math.floor
      (salePrice * TAX_RATE * shopPriceRate * timeSaleRate);
  }
}
 
----------------
 
なお、バージョン管理システムGitのブランチやマージの機能を使えば、これらの統合の作業が楽になります。
機械的に統合できる箇所は自動で統合できますし、機械的に統合できないブランチについても以下のような方法を用いて比較的容易に統合することができます。
(具体的な操作方法は省略します。操作方法はWebで調べられるので、必要に応じて調べると良いでしょう。)
 
・Gitが出力した差分を見てエディタで編集
・競合の原因となったコミットを取り消しコミットし直し
・マスター管理しているブランチを元に新たにブランチを作り直し、作り直したブランチに対して同じ修正を行う