After the person who wrote the bad code left...

Click on "Uncle Wheat" above and select "Top/Star Public Account"

Welfare dry goods, delivered as soon as possible

Hello everyone, I'm Mai, when the person who wrote the bad code left, when we started to take over other people's projects.

He left a lot of mess and faced mountains of shit. At this time, we may face a problem, should we refactor ?

In most cases, the code works, we try not to touch it, but if we have to refactor, what should we do? What are the principles and techniques of refactoring? Share it with you below.

For specific refactoring methods, please refer to "Code Encyclopedia 2" or "Refactoring: Improving the Design of Existing Code". This article will no longer focus on the axe, but focus on some superficial "methodologies" during refactoring, aiming to improve the efficiency of refactoring.

The author does not use heavyweight refactoring tools, but only uses the "Smart Rename" function of Source Insight. It does not use unit testing tools such as CUnit, but ensures the correctness of the code through online debugging and automated testing.

a background

The MDU series products are taken over from other places, and there are only three or five people related to the OMCI module, including the author. In addition to the development of new features, a lot of time is spent dealing with legacy bugs. However, the code of this module is complex and poorly readable, which makes it difficult to use and maintain it with confidence.

Plus, being busy can be disorienting. When the main time and energy are spent on troubleshooting, naturally there is no time to consider rectifying the code, and thus fall into the embarrassing situation of fighting fires everywhere and running for life.

two goals

The main purpose of refactoring is to improve the design of existing code, not to fix bugs, add new features, etc.

Refactoring can be a simple physical refactoring such as changing variable names and rearranging directories, or a slightly complex logical refactoring such as extracting sub-functions and simplifying redundant designs. But neither changes the functionality of existing code.

Refactoring can turn spaghetti-like messy code into lasagna-like clean code. Clean code is more robust because it makes it easier to build a good test net. At the same time, novices and seniors can modify it with confidence.

It is expected that after the refactoring, the code logic is clear at a glance, the extension and modification are very convenient, and the fault can be quickly located and repaired. Where predecessors have wrestled, future generations will no longer fall down, and future generations can directly borrow the results that predecessors have thought. In short, it is highly humanized and greatly liberates human and brain power.

The original idea was to establish a test protection system by refactoring part of the process and code (code first), generate phase reports, and show code quality (instances plus data) and failure convergence curves. With the help of such a report, it is expected to gain the support and publicity of the leadership, which is also conducive to performance appraisal.

Three Practices

In specific practice, the author does not perform pure "refactoring", but also does defect modification and adds auxiliary functions such as automated testing. In principle, the existing code should be refactored, and the new code should be reused.

3.1 Code study

The OMCI module code is complex, with many branches, and it is difficult to get started (it is said that it is difficult to get started in half a year, and can only be proficient in one year). If the existing code cannot be effectively mastered, it will inevitably be forced to spend time to be healthy and not recognized by the project (in fact, the remaining faults found in the module are endless). On the contrary, if the existing code can be fully grasped, it is possible to transform the module into easier development and maintenance through reverse engineering, system/code recovery and refactoring, and finally liberate the coders themselves.

In order to improve the efficiency of code study, the method of division of labor and code comments can be used.

"Division of labor and reading" refers to dividing the module into several sub-functions (such as protocol analysis, alarm, statistics, layer 2, voice, etc.), each person in the group is responsible for one or several blocks, and communicates and rotates irregularly.

"Code commenting" means that in the process of learning the code, you can annotate the code (such as processes, functions, and small lines of code), functions, intentions, skills, defects, questions, etc. remarks). Among them, "questions" can be converted into functions or intentions by consulting colleagues in the same module of brother products, or answered by other commenters.

The advantages of doing so are: avoid duplication of study; experience accumulation; quantification.

The code can take the latest version of the product, and establish a server public code directory (SVN management is better). Don't overwrite other people's comments when commenting.

It is recommended that the comments be in a unified format for easy identification and retrieval, such as "//>". An example code comment is shown below:

case OMCI_ME_ATTRIBUTE_2: // Operational state
     if (attr.attr.ucOperationState != 0 && attr.attr.ucAdminState != 1) //xywang0618> BUG: should be ucOperationState!
     {
         return OMCI_FUNC_RETURN_OUT_OF_RANGE;
     }
     break;

3.2 Readability

First, standardize the naming of variables, functions, etc. The specific method will not be repeated here.

Second, comments are in place, especially global variables and generic functions. An example is as follows:

/******************************************************************************
* 函数名称:  ByteArray2StrSeq
* 功能说明:  掩码字节数组字符串化
            该数组元素为掩码字节,将其所有值为1的比特位置转换为指定格式的字符串
* 输入参数:  pucByteArray: 掩码字节数组
            ucByteNum   : 掩码字节数组待转换的有效字节数目
            ucBaseVal   : 掩码字符串起始字节对应的数值
* 输出参数:  pStrSeq     :掩码字符串,以','、'-'间隔
            形如0xD7(0b'11010111)  ---> "0-1,3,5-7"
* 返 回 值:  pStr        :pStrSeq的指针备份,可用于strlen等链式表达式
* 用法示例:  INT8U aucByteArray[8] = {0xD7, 0x8F, 0xF5, 0x73};
            CHAR szSeq[64] = {0};
            ByteArray2StrSeq(aucByteArray, 4, 0, szSeq);
               ----> "0-1,3,5-8,12-19,21,23,25-27,30-31"
            memset(szSeq, 0, sizeof(szSeq));
            ByteArray2StrSeq(aucByteArray, 4, 1, szSeq);
               ----> "1-2,4,6-9,13-20,22,24,26-28,31-32"
* 注意事项:  因本函数内含strcat,故调用前应按需初始化pStrSeq
******************************************************************************/
CHAR *ByteArray2StrSeq(INT8U *pucByteArray, INT8U ucByteNum, INT8U ucBaseVal, CHAR *pStrSeq);

Finally, rectify the obscure code. There are two main methods:

1) Rewrite method

Taking PON optical path detection as an example, the optical power unit provided by the underlying interface is 0.1uW, the optical power unit reported by the OMCI protocol Test message is 0.002dBuW, and the Ani-G power attribute unit is 0.002dBmW.

The original code conversion is as follows (adapted for emphasis):

INT16S wRxPower = GetRxPowerInDot1uW(); //接收光功率
if(wRxPower < 1){
    wRxPower = 1;
}
/*0.1uw to 0.002dbm*/
dblVal = 10 * log10(wRxPower) - 40;
dblVal = dblVal * 500;
wRxPower = (INT16U)dblVal;
wRxPower  = (int)wRxPower*100;

/*opt pwr  0.00002db      X  * 0.00002*/
wRxPower = wRxPower + (30 * 500) * 100;
if(wRxPower < 0){
    val = (INT16U)((0 - wRxPower) / 100);
    val = (((~val) & 0x7fff) + 1) | 0x8000;
    wRxPower = val;
}
else{
    wRxPower = wRxPower / 100;
}

It can be seen that the conversion relationship in the original implementation is very obscure and difficult to understand. In fact, with the help of the two formulas 1dBuW=10*lg(1uW) and 1dBuW-1dBmW=30dB, a more concise and easy-to-understand expression can be obtained after simple mathematical derivation (adapted for emphasis):

INT16S wRxPower = GetRxPowerInDot1uW(); //接收光功率
//Test单位0.002dBuW,底层单位0.1uW,转换关系T=(10*lg(B*0.1))/0.002=5000*(lgB-1)
wRxPower = (INT16S)(5000 * (log10((DOUBLE)wRxPower)-1));

//Ani-G功率属性单位0.002dBmW,Test结果单位0.002dBuW
//转换关系A(dBmW)*0.002 + 30 = T(dBuW)*0.002,即A=T-15000
INT16S wAniRxPwr = wRxPower - 15000;

Note that the original implementation mistakenly believed that the Ani-G power property was in the same unit as the Test result, and the new implementation has corrected this error.

2) Wrapper function

Taking the mask verification of entity attributes as an example, the original code is as follows:

/*掩码初校验*/
if ((OMCIMETYPE_SET == vpIn->omci_header.ucmsgtype)
 || (OMCIMETYPE_GET == vpIn->omci_header.ucmsgtype))
{
    wMask = W(response.omcimsg.auccontent[0],response.omcimsg.auccontent[1]);
    usSupportMask = (1 << (OMCI_ATTRIBUTE_NUMBER - map.num))-1;
    if( 0 != (wMask & usSupportMask))
    {
        OmciPrint_warn("[%s] check mask warning: (meclass[%u], meid[%u], msgtype[%u], mask[0x%x], unsupport mask[0x%x])!\n\r",
                       FUNCTION_NAME, vpIn->omci_header.wmeclass, vpIn->omci_header.wmeid, vpIn->omci_header.ucmsgtype, wMask, usSupportMask);
    }
}

Statements for assigning and judging usSupportMask (lines 6 to 7) are used to check whether the mask is out of bounds. For more readability, encapsulate it as a function like this:

/******************************************************************************
* 函数名称:  OmciIsMaskOutOfLimit
* 功能说明:  判断实体属性掩码是否越界(比特1数目超过属性数目)
* 输入参数:  INT16U wMeMask  :实体掩码
*           INT8U ucAttrNum :属性数目
* 输出参数:  NA
* 返 回 值:  BOOL
******************************************************************************/
BOOL OmciIsMaskOutOfLimit(INT16U wMeMask, INT8U ucAttrNum)
{
    //wMeMask     :mmmm mmmm mmm0 m000
    //wInvertMask :0000 0000 000i iiii
    INT8U wInvertMask = (1 << (OMCI_ATTR_MAX_NUM-ucAttrNum)) - 1;
    return (0 != (wMeMask & wInvertMask));
}

The encapsulated function name is properly "self-describing".

3.3 Online commissioning project

As an embedded terminal, the product needs to be compiled and packaged in the Linux system, and then downloaded to the target board to run. This cross-compilation method is undoubtedly inefficient for the debugging of a single module.

To improve the commissioning efficiency, build an online commissioning project on the Linux server. That is, extract the OMCI module code, compile and run it directly on the server after a little modification. This avoids the need to restart the board to upgrade the large version every time the code is modified, and the commissioning efficiency is extremely high.

In order to make the module run independently, it is necessary to write a mock interface to shield the underlying calls, and to cut down unnecessary features (such as threading and communication).

3.4 Simulation database

The OMCI module uses an in-memory database to manage entity information that needs to be persisted, but a large number of platform-related interfaces are called in the database code, which is not conducive to online debugging of the module. Therefore, the author wrote a mock database after studying the source code. The library imitates several original library interfaces and behaviors used by the module, and the internal verification of the simulated interface adds error information printing to facilitate troubleshooting.

In addition, the unified interface is encapsulated twice on the basis of database interface primitives, which eliminates the clutter and duplication of database operation codes in the module in one fell swoop.

3.5 Automated Testing

Reconstruction without testing the protective net is tantamount to surgery without a blood source.

First, the public interfaces and functions are provided with corresponding test functions, which serve as both examples and use cases. Such as:

//Start of ByteArray2StrSeqTest//
VOID ByteArray2StrSeqTest(VOID)
{
    //ByteArray2StrSeq函数算法不甚优美和严谨,应多加测试验证,如有可能尽量优化。
    INT8U ucTestIndex = 1;
    INT8U pucByteArray[] = {0xD7, 0x8F, 0xF5, 0x73, 0xB7, 0xF0, 0x00, 0xE8, 0x2C, 0x3B};
    CHAR pStrSeq[50] = {0};

    //Time Consumed(x86_gcc3.2.3_glibc2.2.5): 72us
    memset(pStrSeq, 0, sizeof(pStrSeq));
    ByteArray2StrSeq(pucByteArray, 4, 1, pStrSeq);
    printf("[%s]<Test Case %u> Result: %s, pStrSeq = %s!\n", __FUNCTION__, ucTestIndex++,
           strcmp(pStrSeq, "1-2,4,6-9,13-20,22,24,26-28,31-32") ? "ERROR" : "OK", pStrSeq);

    //Time Consumed(x86_gcc3.2.3_glibc2.2.5): 7us
    memset(pStrSeq, 0, sizeof(pStrSeq));
    ByteArray2StrSeq(pucByteArray, 4, 0, pStrSeq);
    printf("[%s]<Test Case %u> Result: %s, pStrSeq = %s!!!\n", __FUNCTION__, ucTestIndex++,
           strcmp(pStrSeq, "0-1,3,5-8,12-19,21,23,25-27,30-31") ? "ERROR" : "OK", pStrSeq);

    //Time Consumed(x86_gcc3.2.3_glibc2.2.5): 4us
    memset(pStrSeq, 0, sizeof(pStrSeq));
    ByteArray2StrSeq(&pucByteArray[4], 2, 1, pStrSeq);
    printf("[%s]<Test Case %u> Result: %s, pStrSeq = %s!\n", __FUNCTION__, ucTestIndex++,
           strcmp(pStrSeq, "1,3-4,6-12") ? "ERROR" : "OK", pStrSeq);

    //Time Consumed(x86_gcc3.2.3_glibc2.2.5): 4us
    memset(pStrSeq, 0, sizeof(pStrSeq));
    ByteArray2StrSeq(&pucByteArray[6], 2, 1, pStrSeq);
    printf("[%s]<Test Case %u> Result: %s, pStrSeq = %s!\n", __FUNCTION__, ucTestIndex++,
           strcmp(pStrSeq, "9-11,13") ? "ERROR" : "OK", pStrSeq);

    //Time Consumed(x86_gcc3.2.3_glibc2.2.5): 5us
    memset(pStrSeq, 0, sizeof(pStrSeq));
    ByteArray2StrSeq(&pucByteArray[8], 2, 1, pStrSeq);
    printf("[%s]<Test Case %u> Result: %s, pStrSeq = %s!\n", __FUNCTION__, ucTestIndex++,
           strcmp(pStrSeq, "3,5-6,11-13,15-16") ? "ERROR" : "OK", pStrSeq);
}
//End of ByteArray2StrSeqTest//

In addition, an automated test function (TestSuite) is added to the module, which can be used to verify the configuration and query operations of batches or individual entities. The statistics of batch test results are as follows (the specific test results of each entity are omitted):

457fdc2706c0f1bead2ffd26e62d8bcd.png

Among the above test results, Failed TestCase(s) is the most critical, indicating the number of failed test cases. In addition, UnCompared TestCase(s) represents the number of items that have not been compared, such as entities with volatile attributes such as acquisition time, which cannot be preset with appropriate expected results, so no comparisons are made. The printing information during the test can be saved as a log file, and then search the failure keyword in the printing log to learn which configuration failed.

When a large number of changes are made to the current code, with the help of the above automated testing functions, the impact of the modification results can be quickly known. When developing new functions, you can first design test cases and expected results, and then code according to the "test-driven development" mode to improve coding efficiency and accuracy.

3.6 Straight to the core

The traditional refactoring steps are easy first, then difficult, and peripheral first and then core. The author does the opposite, first refactoring the core public code. The benefits of doing this are:

1) It is easy to sort out the header file inclusion relationship

In the online debugging project, only the most common code files (such as the log function) are initially reserved. After refactoring and debugging, the target code of other single functions is gradually added. The process splits and/or combines files as needed, reducing nesting and cross-referencing of header files.

2) Avoid duplication of work or even rework

After the common code is refactored and encapsulated, it will be easier to eliminate redundancy when refactoring the more peripheral application code. If the peripheral code is refactored first, it is likely to find that some logic can be unified into the common code, which will lead to large-scale rework; while if the common code is refactored first, it is easy to study the usage of the peripheral code. Screen for these redundancies.

3) Iterative verification

When the peripheral code is gradually superimposed on the refactored common code, the correctness and ease of use of the common code are also repeatedly tested.

4) Boost confidence

The process of first core, then peripheral, and gradually superimposed verification is controllable, which can enhance confidence and relieve pressure during large-scale reconstruction. On the contrary, if the peripheral code is refactored first, and when the core is touched, the whole body will be affected, and the pressure will be extremely high.

Four effects

On the basis of a certain product code, refactor the OMCI module DB/LOG/entity access/message processing/performance statistics. After more than three months of refactoring, the complexity of the module code has been greatly reduced (the average complexity of a core source file has been reduced to 1/4 of the original), and the code has been significantly simplified (more than 10,000 lines have been simplified according to incomplete statistics). readability. In the process of adding code, a large number of tool macros and functions are written, and practical functions such as OMCI automated testing and memory detection are added.

The refactoring effect of a function code is measured by LineCount and Source Monitor, as shown in the following table:

b75a138e9466009fb4030f52ed56fc80.png

In addition, the general framework, code and experience accumulated during the refactoring process can be further applied to new projects.


Original: https://www.cnblogs.com/clover-toeic/p/3842758.html

Disclaimer: This article comes from the Internet, and the copyright belongs to the original author. If there are copyright issues, please contact to delete.

—— The End ——

Recommended in the past

I'm stumped, what is the role of C language enumeration end?

30 solutions to common problems with single-chip microcomputers! Normal people I don't tell them

Musk's brain-computer interface can be made with a Raspberry Pi?

3 open source libraries commonly used by experts, making MCU development more efficient

Seemingly simple code, but hidden secrets...

How to prevent cracking? MCU encryption technology revealed

Click on the card above to follow me

c2f8285baebcb8cdfbed08ffa3f5f657.png

Everything you ordered looks good , I take it seriously as I like it

Guess you like

Origin blog.csdn.net/u010632165/article/details/123343662