Chuyển đổi từ mã thủ tục sang mã hướng đối tượng


16

Tôi đã đọc Làm việc hiệu quả với Legacy CodeClean Code với mục tiêu tìm hiểu các chiến lược về cách bắt đầu làm sạch cơ sở mã hiện có của một ứng dụng webforms ASP.NET lớn.

Hệ thống này đã có từ năm 2005 và kể từ đó đã trải qua một số cải tiến. Ban đầu mã được cấu trúc như sau (và phần lớn vẫn được cấu trúc theo cách này):

  • ASP.NET (aspx / ascx)
  • Mã phía sau (c #)
  • Lớp logic nghiệp vụ (c #)
  • Lớp truy cập dữ liệu (c #)
  • Cơ sở dữ liệu (Oracle)

Vấn đề chính là mã được giả mạo theo thủ tục. Nó hầu như vi phạm tất cả các hướng dẫn được mô tả trong cả hai cuốn sách.

Đây là một ví dụ về một lớp điển hình trong Lớp Logic nghiệp vụ:

    public class AddressBO
{
    public TransferObject GetAddress(string addressID)
    {
        if (StringUtils.IsNull(addressID))
        {
            throw new ValidationException("Address ID must be entered");
        }

        AddressDAO addressDAO = new AddressDAO();
        return addressDAO.GetAddress(addressID);
    }

    public TransferObject Insert(TransferObject addressDetails)
    {
        if (StringUtils.IsNull(addressDetails.GetString("EVENT_ID")) ||
            StringUtils.IsNull(addressDetails.GetString("LOCALITY")) ||
            StringUtils.IsNull(addressDetails.GetString("ADDRESS_TARGET")) ||
            StringUtils.IsNull(addressDetails.GetString("ADDRESS_TYPE_CODE")) ||
            StringUtils.IsNull(addressDetails.GetString("CREATED_BY")))
        {
            throw new ValidationException(
                "You must enter an Event ID, Locality, Address Target, Address Type Code and Created By.");
        }

        string addressID = Sequence.GetNextValue("ADDRESS_ID_SEQ");
        addressDetails.SetValue("ADDRESS_ID", addressID);

        string syncID = Sequence.GetNextValue("SYNC_ID_SEQ");
        addressDetails.SetValue("SYNC_ADDRESS_ID", syncID);

        TransferObject syncDetails = new TransferObject();

        Transaction transaction = new Transaction();

        try
        {
            AddressDAO addressDAO = new AddressDAO();
            addressDAO.Insert(addressDetails, transaction);

            // insert the record for the target
            TransferObject addressTargetDetails = new TransferObject();
            switch (addressDetails.GetString("ADDRESS_TARGET"))
            {
                case "PARTY_ADDRESSES":
                    {
                        addressTargetDetails.SetValue("ADDRESS_ID", addressID);
                        addressTargetDetails.SetValue("ADDRESS_TYPE_CODE",
                                                      addressDetails.GetString("ADDRESS_TYPE_CODE"));
                        addressTargetDetails.SetValue("PARTY_ID", addressDetails.GetString("PARTY_ID"));
                        addressTargetDetails.SetValue("EVENT_ID", addressDetails.GetString("EVENT_ID"));
                        addressTargetDetails.SetValue("CREATED_BY", addressDetails.GetString("CREATED_BY"));

                        addressDAO.InsertPartyAddress(addressTargetDetails, transaction);

                        break;
                    }
                case "PARTY_CONTACT_ADDRESSES":
                    {
                        addressTargetDetails.SetValue("ADDRESS_ID", addressID);
                        addressTargetDetails.SetValue("ADDRESS_TYPE_CODE",
                                                      addressDetails.GetString("ADDRESS_TYPE_CODE"));
                        addressTargetDetails.SetValue("PUBLIC_RELEASE_FLAG",
                                                      addressDetails.GetString("PUBLIC_RELEASE_FLAG"));
                        addressTargetDetails.SetValue("CONTACT_ID", addressDetails.GetString("CONTACT_ID"));
                        addressTargetDetails.SetValue("EVENT_ID", addressDetails.GetString("EVENT_ID"));
                        addressTargetDetails.SetValue("CREATED_BY", addressDetails.GetString("CREATED_BY"));

                        addressDAO.InsertContactAddress(addressTargetDetails, transaction);

                        break;
                    }

                << many more cases here >>
                default:
                    {
                        break;
                    }
            }

            // synchronise
            SynchronisationBO synchronisationBO = new SynchronisationBO();
            syncDetails = synchronisationBO.Synchronise("I", transaction,
                                                        "ADDRESSES", addressDetails.GetString("ADDRESS_TARGET"),
                                                        addressDetails, addressTargetDetails);


            // commit
            transaction.Commit();
        }
        catch (Exception)
        {
            transaction.Rollback();
            throw;
        }

        return new TransferObject("ADDRESS_ID", addressID, "SYNC_DETAILS", syncDetails);
    }


    << many more methods are here >>

}

Nó có rất nhiều sự trùng lặp, lớp có một số trách nhiệm, v.v. - nó thường chỉ là mã 'không sạch'.

Tất cả các mã trong toàn hệ thống phụ thuộc vào việc triển khai cụ thể.

Đây là một ví dụ về một lớp điển hình trong Lớp truy cập dữ liệu:

    public class AddressDAO : GenericDAO
{
    public static readonly string BASE_SQL_ADDRESSES =
        "SELECT " +
        "  a.address_id, " +
        "  a.event_id, " +
        "  a.flat_unit_type_code, " +
        "  fut.description as flat_unit_description, " +
        "  a.flat_unit_num, " +
        "  a.floor_level_code, " +
        "  fl.description as floor_level_description, " +
        "  a.floor_level_num, " +
        "  a.building_name, " +
        "  a.lot_number, " +
        "  a.street_number, " +
        "  a.street_name, " +
        "  a.street_type_code, " +
        "  st.description as street_type_description, " +
        "  a.street_suffix_code, " +
        "  ss.description as street_suffix_description, " +
        "  a.postal_delivery_type_code, " +
        "  pdt.description as postal_delivery_description, " +
        "  a.postal_delivery_num, " +
        "  a.locality, " +
        "  a.state_code, " +
        "  s.description as state_description, " +
        "  a.postcode, " +
        "  a.country, " +
        "  a.lock_num, " +
        "  a.created_by, " +
        "  TO_CHAR(a.created_datetime, '" + SQL_DATETIME_FORMAT + "') as created_datetime, " +
        "  a.last_updated_by, " +
        "  TO_CHAR(a.last_updated_datetime, '" + SQL_DATETIME_FORMAT + "') as last_updated_datetime, " +
        "  a.sync_address_id, " +
        "  a.lat," +
        "  a.lon, " +
        "  a.validation_confidence, " +
        "  a.validation_quality, " +
        "  a.validation_status " +
        "FROM ADDRESSES a, FLAT_UNIT_TYPES fut, FLOOR_LEVELS fl, STREET_TYPES st, " +
        "     STREET_SUFFIXES ss, POSTAL_DELIVERY_TYPES pdt, STATES s " +
        "WHERE a.flat_unit_type_code = fut.flat_unit_type_code(+) " +
        "AND   a.floor_level_code = fl.floor_level_code(+) " +
        "AND   a.street_type_code = st.street_type_code(+) " +
        "AND   a.street_suffix_code = ss.street_suffix_code(+) " +
        "AND   a.postal_delivery_type_code = pdt.postal_delivery_type_code(+) " +
        "AND   a.state_code = s.state_code(+) ";


    public TransferObject GetAddress(string addressID)
    {
        //Build the SELECT Statement
        StringBuilder selectStatement = new StringBuilder(BASE_SQL_ADDRESSES);

        //Add WHERE condition
        selectStatement.Append(" AND a.address_id = :addressID");

        ArrayList parameters = new ArrayList{DBUtils.CreateOracleParameter("addressID", OracleDbType.Decimal, addressID)};

        // Execute the SELECT statement
        Query query = new Query();
        DataSet results = query.Execute(selectStatement.ToString(), parameters);

        // Check if 0 or more than one rows returned
        if (results.Tables[0].Rows.Count == 0)
        {
            throw new NoDataFoundException();
        }
        if (results.Tables[0].Rows.Count > 1)
        {
            throw new TooManyRowsException();
        }

        // Return a TransferObject containing the values
        return new TransferObject(results);
    }


    public void Insert(TransferObject insertValues, Transaction transaction)
    {
        // Store Values
        string addressID = insertValues.GetString("ADDRESS_ID");
        string syncAddressID = insertValues.GetString("SYNC_ADDRESS_ID");
        string eventID = insertValues.GetString("EVENT_ID");
        string createdBy = insertValues.GetString("CREATED_BY");

        // postal delivery
        string postalDeliveryTypeCode = insertValues.GetString("POSTAL_DELIVERY_TYPE_CODE");
        string postalDeliveryNum = insertValues.GetString("POSTAL_DELIVERY_NUM");

        // unit/building
        string flatUnitTypeCode = insertValues.GetString("FLAT_UNIT_TYPE_CODE");
        string flatUnitNum = insertValues.GetString("FLAT_UNIT_NUM");
        string floorLevelCode = insertValues.GetString("FLOOR_LEVEL_CODE");
        string floorLevelNum = insertValues.GetString("FLOOR_LEVEL_NUM");
        string buildingName = insertValues.GetString("BUILDING_NAME");

        // street
        string lotNumber = insertValues.GetString("LOT_NUMBER");
        string streetNumber = insertValues.GetString("STREET_NUMBER");
        string streetName = insertValues.GetString("STREET_NAME");
        string streetTypeCode = insertValues.GetString("STREET_TYPE_CODE");
        string streetSuffixCode = insertValues.GetString("STREET_SUFFIX_CODE");

        // locality/state/postcode/country
        string locality = insertValues.GetString("LOCALITY");
        string stateCode = insertValues.GetString("STATE_CODE");
        string postcode = insertValues.GetString("POSTCODE");
        string country = insertValues.GetString("COUNTRY");

        // esms address
        string esmsAddress = insertValues.GetString("ESMS_ADDRESS");

        //address/GPS
        string lat = insertValues.GetString("LAT");
        string lon = insertValues.GetString("LON");
        string zoom = insertValues.GetString("ZOOM");

        //string validateDate = insertValues.GetString("VALIDATED_DATE");
        string validatedBy = insertValues.GetString("VALIDATED_BY");
        string confidence = insertValues.GetString("VALIDATION_CONFIDENCE");
        string status = insertValues.GetString("VALIDATION_STATUS");
        string quality = insertValues.GetString("VALIDATION_QUALITY");


        // the insert statement
        StringBuilder insertStatement = new StringBuilder("INSERT INTO ADDRESSES (");
        StringBuilder valuesStatement = new StringBuilder("VALUES (");

        ArrayList parameters = new ArrayList();

        // build the insert statement
        insertStatement.Append("ADDRESS_ID, EVENT_ID, CREATED_BY, CREATED_DATETIME, LOCK_NUM ");
        valuesStatement.Append(":addressID, :eventID, :createdBy, SYSDATE, 1 ");
        parameters.Add(DBUtils.CreateOracleParameter("addressID", OracleDbType.Decimal, addressID));
        parameters.Add(DBUtils.CreateOracleParameter("eventID", OracleDbType.Decimal, eventID));
        parameters.Add(DBUtils.CreateOracleParameter("createdBy", OracleDbType.Varchar2, createdBy));

        // build the insert statement
        if (!StringUtils.IsNull(syncAddressID))
        {
            insertStatement.Append(", SYNC_ADDRESS_ID");
            valuesStatement.Append(", :syncAddressID");
            parameters.Add(DBUtils.CreateOracleParameter("syncAddressID", OracleDbType.Decimal, syncAddressID));
        }

        if (!StringUtils.IsNull(postalDeliveryTypeCode))
        {
            insertStatement.Append(", POSTAL_DELIVERY_TYPE_CODE");
            valuesStatement.Append(", :postalDeliveryTypeCode ");
            parameters.Add(DBUtils.CreateOracleParameter("postalDeliveryTypeCode", OracleDbType.Varchar2, postalDeliveryTypeCode));
        }

        if (!StringUtils.IsNull(postalDeliveryNum))
        {
            insertStatement.Append(", POSTAL_DELIVERY_NUM");
            valuesStatement.Append(", :postalDeliveryNum ");
            parameters.Add(DBUtils.CreateOracleParameter("postalDeliveryNum", OracleDbType.Varchar2, postalDeliveryNum));
        }

        if (!StringUtils.IsNull(flatUnitTypeCode))
        {
            insertStatement.Append(", FLAT_UNIT_TYPE_CODE");
            valuesStatement.Append(", :flatUnitTypeCode ");
            parameters.Add(DBUtils.CreateOracleParameter("flatUnitTypeCode", OracleDbType.Varchar2, flatUnitTypeCode));
        }

        if (!StringUtils.IsNull(lat))
        {
            insertStatement.Append(", LAT");
            valuesStatement.Append(", :lat ");
            parameters.Add(DBUtils.CreateOracleParameter("lat", OracleDbType.Decimal, lat));
        }

        if (!StringUtils.IsNull(lon))
        {
            insertStatement.Append(", LON");
            valuesStatement.Append(", :lon ");
            parameters.Add(DBUtils.CreateOracleParameter("lon", OracleDbType.Decimal, lon));
        }

        if (!StringUtils.IsNull(zoom))
        {
            insertStatement.Append(", ZOOM");
            valuesStatement.Append(", :zoom ");
            parameters.Add(DBUtils.CreateOracleParameter("zoom", OracleDbType.Decimal, zoom));
        }

        if (!StringUtils.IsNull(flatUnitNum))
        {
            insertStatement.Append(", FLAT_UNIT_NUM");
            valuesStatement.Append(", :flatUnitNum ");
            parameters.Add(DBUtils.CreateOracleParameter("flatUnitNum", OracleDbType.Varchar2, flatUnitNum));
        }

        if (!StringUtils.IsNull(floorLevelCode))
        {
            insertStatement.Append(", FLOOR_LEVEL_CODE");
            valuesStatement.Append(", :floorLevelCode ");
            parameters.Add(DBUtils.CreateOracleParameter("floorLevelCode", OracleDbType.Varchar2, floorLevelCode));
        }

        if (!StringUtils.IsNull(floorLevelNum))
        {
            insertStatement.Append(", FLOOR_LEVEL_NUM");
            valuesStatement.Append(", :floorLevelNum ");
            parameters.Add(DBUtils.CreateOracleParameter("floorLevelNum", OracleDbType.Varchar2, floorLevelNum));
        }

        if (!StringUtils.IsNull(buildingName))
        {
            insertStatement.Append(", BUILDING_NAME");
            valuesStatement.Append(", :buildingName ");
            parameters.Add(DBUtils.CreateOracleParameter("buildingName", OracleDbType.Varchar2, buildingName));
        }

        if (!StringUtils.IsNull(lotNumber))
        {
            insertStatement.Append(", LOT_NUMBER");
            valuesStatement.Append(", :lotNumber ");
            parameters.Add(DBUtils.CreateOracleParameter("lotNumber", OracleDbType.Varchar2, lotNumber));
        }

        if (!StringUtils.IsNull(streetNumber))
        {
            insertStatement.Append(", STREET_NUMBER");
            valuesStatement.Append(", :streetNumber ");
            parameters.Add(DBUtils.CreateOracleParameter("streetNumber", OracleDbType.Varchar2, streetNumber));
        }

        if (!StringUtils.IsNull(streetName))
        {
            insertStatement.Append(", STREET_NAME");
            valuesStatement.Append(", :streetName ");
            parameters.Add(DBUtils.CreateOracleParameter("streetName", OracleDbType.Varchar2, streetName));
        }

        if (!StringUtils.IsNull(streetTypeCode))
        {
            insertStatement.Append(", STREET_TYPE_CODE");
            valuesStatement.Append(", :streetTypeCode ");
            parameters.Add(DBUtils.CreateOracleParameter("streetTypeCode", OracleDbType.Varchar2, streetTypeCode));
        }

        if (!StringUtils.IsNull(streetSuffixCode))
        {
            insertStatement.Append(", STREET_SUFFIX_CODE");
            valuesStatement.Append(", :streetSuffixCode ");
            parameters.Add(DBUtils.CreateOracleParameter("streetSuffixCode", OracleDbType.Varchar2, streetSuffixCode));
        }

        if (!StringUtils.IsNull(locality))
        {
            insertStatement.Append(", LOCALITY");
            valuesStatement.Append(", :locality");
            parameters.Add(DBUtils.CreateOracleParameter("locality", OracleDbType.Varchar2, locality));
        }

        if (!StringUtils.IsNull(stateCode))
        {
            insertStatement.Append(", STATE_CODE");
            valuesStatement.Append(", :stateCode");
            parameters.Add(DBUtils.CreateOracleParameter("stateCode", OracleDbType.Varchar2, stateCode));
        }

        if (!StringUtils.IsNull(postcode))
        {
            insertStatement.Append(", POSTCODE");
            valuesStatement.Append(", :postcode ");
            parameters.Add(DBUtils.CreateOracleParameter("postcode", OracleDbType.Varchar2, postcode));
        }

        if (!StringUtils.IsNull(country))
        {
            insertStatement.Append(", COUNTRY");
            valuesStatement.Append(", :country ");
            parameters.Add(DBUtils.CreateOracleParameter("country", OracleDbType.Varchar2, country));
        }

        if (!StringUtils.IsNull(esmsAddress))
        {
            insertStatement.Append(", ESMS_ADDRESS");
            valuesStatement.Append(", :esmsAddress ");
            parameters.Add(DBUtils.CreateOracleParameter("esmsAddress", OracleDbType.Varchar2, esmsAddress));
        }

        if (!StringUtils.IsNull(validatedBy))
        {
            insertStatement.Append(", VALIDATED_DATE");
            valuesStatement.Append(", SYSDATE ");
            insertStatement.Append(", VALIDATED_BY");
            valuesStatement.Append(", :validatedBy ");
            parameters.Add(DBUtils.CreateOracleParameter("validatedBy", OracleDbType.Varchar2, validatedBy));
        }


        if (!StringUtils.IsNull(confidence))
        {
            insertStatement.Append(", VALIDATION_CONFIDENCE");
            valuesStatement.Append(", :confidence ");
            parameters.Add(DBUtils.CreateOracleParameter("confidence", OracleDbType.Decimal, confidence));
        }

        if (!StringUtils.IsNull(status))
        {
            insertStatement.Append(", VALIDATION_STATUS");
            valuesStatement.Append(", :status ");
            parameters.Add(DBUtils.CreateOracleParameter("status", OracleDbType.Varchar2, status));
        }

        if (!StringUtils.IsNull(quality))
        {
            insertStatement.Append(", VALIDATION_QUALITY");
            valuesStatement.Append(", :quality ");
            parameters.Add(DBUtils.CreateOracleParameter("quality", OracleDbType.Decimal, quality));
        }

        // finish off the statement
        insertStatement.Append(") ");
        valuesStatement.Append(")");

        // build the insert statement
        string sql = insertStatement + valuesStatement.ToString();

        // Execute the INSERT Statement
        Dml dmlDAO = new Dml();
        int rowsAffected = dmlDAO.Execute(sql, transaction, parameters);

        if (rowsAffected == 0)
        {
            throw new NoRowsAffectedException();
        }
    }

    << many more methods go here >>
}

Hệ thống này được phát triển bởi tôi và một nhóm nhỏ vào năm 2005 sau khóa học .NET 1 tuần. Trước đây, kinh nghiệm của tôi là trong các ứng dụng máy chủ-máy khách. Trong 5 năm qua tôi đã nhận ra lợi ích của thử nghiệm đơn vị tự động, thử nghiệm tích hợp tự động và thử nghiệm chấp nhận tự động (sử dụng Selenium hoặc tương đương) nhưng cơ sở mã hiện tại dường như không thể đưa ra các khái niệm này.

Chúng tôi hiện đang bắt đầu làm việc trên một dự án tăng cường lớn với khung thời gian chặt chẽ. Nhóm bao gồm 5 nhà phát triển .NET - 2 nhà phát triển có vài năm kinh nghiệm .NET và 3 người khác có ít hoặc không có kinh nghiệm .NET. Không ai trong nhóm (bao gồm cả tôi) có kinh nghiệm trong việc sử dụng thử nghiệm đơn vị .NET hoặc khung mô phỏng.

Chiến lược nào bạn sẽ sử dụng để làm cho mã này sạch hơn, hướng đối tượng hơn, có thể kiểm tra và duy trì?


9
Bên cạnh đó, có thể đáng để kiểm tra lại rằng có một sự biện minh chi phí để viết lại hệ thống. Mã cũ có thể xấu, nhưng nếu nó hoạt động đủ tốt, nó có thể rẻ hơn để đặt với các cạnh thô và đầu tư thời gian phát triển của bạn ở nơi khác.
smithco

Một biện minh có thể là giảm nỗ lực và chi phí kiểm tra lại thủ công sau mỗi dự án nâng cao. Vào cuối dự án cuối cùng, thử nghiệm thủ công đã diễn ra trong khoảng 2 tháng. Nếu việc giới thiệu thử nghiệm tự động hơn làm giảm nỗ lực này xuống còn 1-2 tuần thì có thể đáng giá.
Anthony

5
ĐỐI VỚI MÃ HỢP PHÁP, NHÂN VIÊN NÀY LÀ TỐT!
Công việc

Tôi đồng ý rằng đó là hợp lý phù hợp và có cấu trúc. Mục đích chính của tôi là giảm tác dụng phụ của thay đổi. Nỗ lực cần thiết để kiểm tra thủ công toàn bộ ứng dụng sau (và trong) mỗi dự án là rất lớn. Tôi đã nghĩ về việc sử dụng Selenium để kiểm tra nó thông qua phía máy khách - Tôi có một câu hỏi trên ServerFault ( serverfault.com/questions/236546/ trộm ) để nhận đề xuất về việc nhanh chóng hoàn nguyên cơ sở dữ liệu. Tôi cảm thấy thử nghiệm chấp nhận tự động sẽ nhận được hầu hết các lợi ích mà không phải viết lại lớn.
Anthony

Câu trả lời:


16

Bạn đề cập đến hai cuốn sách trong đó một trong những thông điệp chính là "Quy tắc hướng đạo nam" tức là dọn sạch mã khi bạn chạm vào nó. Nếu bạn có một hệ thống làm việc, một bản viết lại khổng lồ sẽ phản tác dụng. Thay vào đó, khi bạn thêm chức năng mới, hãy đảm bảo bạn cải thiện mã khi nó đứng.

  • Viết các bài kiểm tra đơn vị để bao gồm mã hiện có mà bạn cần thay đổi.
  • Tái cấu trúc mã đó để dễ thay đổi hơn (đảm bảo các bài kiểm tra của bạn vẫn vượt qua).
  • Viết bài kiểm tra cho chức năng mới / sửa đổi
  • Viết mã để vượt qua các bài kiểm tra mới
  • Tái cấu trúc khi cần thiết.

Để tìm hiểu sâu hơn, Feathers nói về việc thử nghiệm ứng dụng tại các đường nối của nó: các điểm logic mà các đơn vị kết nối với nhau. Bạn có thể tận dụng một đường may để tạo ra một sơ khai hoặc một bản giả cho một phụ thuộc để bạn có thể viết các bài kiểm tra xung quanh đối tượng phụ thuộc. Hãy lấy địa chỉ của bạn làm ví dụ

public class AddressBO
{
    public TransferObject GetAddress(string addressID)
    {
        if (StringUtils.IsNull(addressID))
        {
            throw new ValidationException("Address ID must be entered");
        }

        AddressDAO addressDAO = new AddressDAO();
        return addressDAO.GetAddress(addressID);
    }
}

Có một đường nối rõ ràng giữa Địa chỉBO và Địa chỉ. Hãy tạo một giao diện cho Địa chỉDAO và cho phép phần phụ thuộc được thêm vào Địa chỉ.

public interface IAddressDAO
{
  TransferObject GetAddress(addressID);
  //add other interface methods here.
}

public class AddressDAO:GenericDAO, IAddressDAO
{
  public TransferObject GetAddress(string addressID)
  {
    ///implementation goes here
  }
}

Bây giờ bạn bác sĩ lên Địa chỉ của bạn để cho phép tiêm

public class AddressBO
{
    private IAddressDAO _addressDAO;
    public AddressBO()
    {
      _addressDAO = new AddressDAO();
    }

    public AddressBO(IAddressDAO addressDAO)
    {
      _addressDAO = addressDAO;
    }

    public TransferObject GetAddress(string addressID)
    {
        if (StringUtils.IsNull(addressID))
        {
            throw new ValidationException("Address ID must be entered");
        }
        //call the injected AddressDAO
        return _addressDAO.GetAddress(addressID);
    }
}

Ở đây chúng tôi đang sử dụng "tiêm phụ thuộc của người nghèo." Mục tiêu duy nhất của chúng tôi là phá vỡ đường may và cho phép chúng tôi kiểm tra Địa chỉ. Bây giờ trong các thử nghiệm đơn vị của chúng tôi, chúng tôi có thể tạo ra một IAddressDAO giả và xác thực các tương tác giữa hai đối tượng.


1
Tôi đồng ý - Tôi thích chiến lược này khi tôi đọc về nó. Chúng tôi có thể dành hàng tháng để làm sạch mã mà không thực sự tăng thêm giá trị. Nếu chúng ta tập trung vào việc làm sạch những gì chúng ta cần thay đổi trong khi thêm một tính năng mới, chúng ta sẽ có được cả hai thế giới tốt nhất.
Anthony

Thách thức duy nhất là viết các bài kiểm tra đơn vị cho mã hiện có. Trước tiên, tôi nghiêng về các bài kiểm tra cấp cao hơn để chúng tôi có thể cấu trúc lại và thêm các bài kiểm tra đơn vị với độ tin cậy cao hơn.
Anthony

1
Có, điều tốt nhất bạn có thể làm là viết các bài kiểm tra xác minh mã thực hiện những gì nó làm. Bạn có thể thử tạo các bài kiểm tra để xác minh hành vi chính xác ... nhưng bạn có nguy cơ phá vỡ các chức năng khác không được kiểm tra.
Michael Brown

Đây là lời giải thích tốt nhất tôi từng thấy khi đưa Feathers "tìm đường may" vào thực tế. Là một người thông thạo về thủ tục hơn OO, có một mối liên hệ rõ ràng giữa Địa chỉBO và Địa chỉ không rõ ràng đối với tôi, nhưng ví dụ này thực sự hữu ích.
SeraM

5

Nếu tôi nhớ đúng Làm việc hiệu quả với Mã kế thừa nói rằng việc viết lại đầy đủ không đảm bảo rằng mã mới sẽ tốt hơn mã cũ (theo quan điểm về chức năng / khiếm khuyết). Các cấu trúc lại trong cuốn sách đó là để sửa lỗi / thêm các tính năng mới.

Một cuốn sách khác mà tôi muốn giới thiệu là Brownfield Application Development in .NET , về cơ bản nói rằng không nên viết lại đầy đủ. Nó nói về việc thực hiện các thay đổi ổn định, lặp đi lặp lại bất cứ khi nào bạn thêm các tính năng mới hoặc sửa lỗi. Nó giải quyết các cân nhắc về chi phí và lợi ích và cảnh báo về việc cố gắng giảm bớt quá nhiều tại một thời điểm. Mặc dù làm việc hiệu quả với Legacy Code nói về cách tái cấu trúc ở cấp độ vi / mã, Phát triển ứng dụng Brownfield trong .NET , chủ yếu bao gồm các cân nhắc ở cấp độ cao hơn khi bao thanh toán lại (cùng với một số công cụ cấp mã).

Cuốn sách Brownfield cũng đề nghị tìm ra khu vực nào của mã gây ra cho bạn nhiều rắc rối nhất và tập trung vào đó. Bất kỳ khu vực nào khác không cần bảo trì nhiều có thể không có giá trị thay đổi.


+1 cho cuốn sách Phát triển ứng dụng Brownfield trong .Net
Gabriel Mongeon

Cảm ơn về lời giới thiệu cuốn sách - Tôi sẽ xem nó. Từ tổng quan, nó sẽ tập trung vào .NET cụ thể hơn hai cuốn sách tôi đã đề cập dường như tập trung vào C, C ++ và Java.
Anthony

4

Đối với một ứng dụng cũ như vậy, sẽ hiệu quả hơn nhiều khi bắt đầu bằng cách bao phủ nó bằng các thử nghiệm tích hợp cấp cao hơn (tự động) thay vì thử nghiệm đơn vị. Sau đó, với các bài kiểm tra tích hợp như mạng an toàn của bạn, bạn có thể bắt đầu tái cấu trúc theo các bước nhỏ nếu phù hợp, nghĩa là nếu chi phí tái cấu trúc sẽ tự trả lại trong thời gian dài. Như những người khác đã lưu ý, điều này không phải là hiển nhiên.

Cũng xem câu trả lời trước đó của tôi cho một câu hỏi tương tự; Hi vọng bạn tìm được thứ hữu dụng.


Tôi đang nghiêng về các bài kiểm tra cấp cao hơn để bắt đầu. Tôi đang làm việc với DBA để tìm ra cách tốt nhất để hoàn nguyên cơ sở dữ liệu sau mỗi lần thực hiện kiểm tra.
Anthony

1

Hãy thật cẩn thận với việc vứt đi và viết lại mã đang chạy ( Những điều bạn không bao giờ nên làm ). Chắc chắn nó có thể xấu, nhưng nếu nó hoạt động để lại. Xem bài đăng trên blog của Joel, chắc chắn nó đã hơn 10 năm tuổi, nhưng nó vẫn đúng mục tiêu.


Tôi không đồng ý với Joel ở đó. Những gì anh ấy nói có thể cảm thấy có liên quan vào thời điểm đó nhưng không phải là viết lại cái mà bây giờ được gọi là Mozilla Firefox?
CashCow

1
Có, nhưng nó đưa Netscape ra khỏi doanh nghiệp trong quá trình! Nó không nói rằng bắt đầu lại không bao giờ là lựa chọn đúng đắn nhưng đó là điều cần phải rất cẩn thận. Và mã OO không phải lúc nào cũng tốt hơn mã thủ tục.
Zachary K

1

Như Mike đã nói, 'quy tắc trinh sát của cậu bé' có lẽ là tốt nhất ở đây, nếu mã hoạt động và không phải là nguồn báo cáo lỗi liên tục, tôi muốn để nó ngồi đó và cải thiện nó từ từ theo thời gian.

Trong dự án nâng cao của bạn cho phép những cách làm mới. Ví dụ, sử dụng ORM cho các tính năng mới và bỏ qua mẫu lớp dữ liệu hiện có. Khi bạn gặp phải các cải tiến cần chạm vào mã hiện có, bạn có thể chuyển một số mã liên quan sang cách làm mới. Sử dụng một mặt tiền hoặc một số bộ điều hợp tại các địa điểm có thể giúp bạn cô lập mã cũ, thậm chí có thể trên mỗi lớp. Điều này có thể giúp bạn bỏ đói mã cũ theo thời gian.

Tương tự, điều này có thể giúp bạn với việc thêm các bài kiểm tra đơn vị, bạn có thể bắt đầu với mã mới mà bạn thực hiện và từ từ thêm một số thử nghiệm cho mã cũ mà bạn phải chạm vào để cải tiến mới.


1

Đó là cả hai cuốn sách tốt. Nếu bạn sẽ bắt đầu viết lại mã theo cách đó, tôi nghĩ điều quan trọng là cũng bắt đầu bao gồm mã bằng các bài kiểm tra đơn vị để giúp giữ ổn định khi bạn viết lại mã.

Nó phải được thực hiện trong các bước nhỏ và sửa đổi loại mã đó có thể dễ dàng gây mất ổn định cho toàn bộ hệ thống.

Tôi sẽ không sửa đổi bất kỳ mã nào bạn không tích cực làm việc. Chỉ làm điều này trên mã bạn đang tích cực nâng cao hoặc sửa chữa. Nếu một cái gì đó phục vụ mục đích của nó nhưng đã không được sửa đổi trong nhiều năm thì hãy bỏ nó đi. Đó là việc của nó ngay cả khi bạn biết một cách tốt hơn.

Vào cuối ngày công ty cần năng suất. Mặc dù mã tốt hơn làm tăng năng suất viết lại mã, chỉ vì nó có thể được viết tốt hơn có lẽ không phải là cách tốt nhất để mang lại giá trị cho sản phẩm của bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.