In-depth Python3 (9) refactoring

0. Summary

  This chapter continues the examples in the previous chapter. Through the process of changing requirements-modifying code-refactoring, you will gradually realize the meaning of unit testing and its importance to refactoring.

1. New error

  Even if you try your best to write comprehensive unit tests, you will still encounter errors. What do I mean by "error"? Errors are test cases that have not been written yet.
Insert picture description here
Insert picture description here

   This code is very simple. Call from _ roman () from\_roman() by passing in an empty stringF R & lt O m _ R & lt O m A n- ( ) , and to ensure that a triggerI nvalid R oman N umeral E rrorThe I n- V A L I D R & lt O m A n- N U m E R & lt A L E R & lt R & lt O R & lt exception. The hard part is finding the error; after finding the error, testing it is an easy job.
Insert picture description here
Insert picture description here
Insert picture description here

  Writing code in this way will make error correction more difficult. Simple errors (like this one) require simple test cases; complex errors will require complex test cases. In a test-centric environment, since it is necessary to accurately describe the error in the code (write test cases) and then correct the error itself, it seems that it takes more time to correct the error. And if the test instance fails to pass correctly, you need to find out whether the correction plan is wrong, and the test instance itself has an error. However, in the long run, this tossing back and forth between the tested code and the tested code is worth it, because it is more likely to correct errors in the first place. At the same time, since all test instances can be easily re-run on the new code, the chance of breaking the old code is lower when the new code is corrected. The unit test of today is the regression test of tomorrow.

2. Control demand changes

  In order to obtain accurate requirements, even though they have tried their best to "nail" customers in place and have experienced the pain of repeated cutting and pasting, the requirements will still change. Most customers don’t know what they want before seeing the product, and even if they do, they are not good at articulating their ideas. And even if they are good at presentation, they will put forward more requirements in the next version. Therefore, it is necessary to be ready to update the test case to respond to changes in requirements.
  For example, suppose we want to expand the range of capabilities of Roman numeral conversion functions. Under normal circumstances, any character in a Roman numeral must not be repeated more than three times in the same line. But the Romans were willing to have an exception to the rule: 4000 is represented by 4 M characters in a line. After this modification, the range of convertible numbers will be expanded from 1...3999 to 1...4999. But first, some modifications must be made to the test case.
Insert picture description here
Insert picture description here

  The existing known values ​​will not change (they are still reasonable test values), but they must be increased within (outside) 4000. Here, I have added 4000 (shortest), 4500 (second shortest), 4888 (longest) and 4999 (largest).
  The definition of "excessive value input" has changed. This test is used to call to_roman() by passing in 4000 and expect to cause an error; currently 4000-4999 are valid values ​​and must be adjusted to 5000.
  The definition of "too many repeated numbers" has also changed. This test calls from_roman() by passing in'MMMM' and expects an error to occur; currently MMMM is recognized as a valid Roman numeral, and this condition must be modified to'MMMMM'.
  Perform a complete loop test for each number in the range, from 1 to 3999. Since the range has been expanded, the for loop also needs to be modified to 4999 as the upper limit.
Insert picture description here
Insert picture description here
Insert picture description here

  Now that we have some test cases that failed due to new requirements, we can consider modifying the code to make it consistent with the new test case (when you first write a unit test, the tested code will never appear "before" the test case. It feels a little strange). Although the coding work is arranged afterwards, there are still many things to do. Once it matches the test case, the coding work can be over. Once you get used to unit testing, you may feel strange that you did not test when you were programming.
Insert picture description here
Insert picture description here

3. Refactor

  Regarding comprehensive unit testing, the most beautiful thing is not the feeling after all the test cases are passed, nor is it that others complain that you broke the code, and you prove through practice that you don't have the pleasure of time. The most beautiful thing about unit testing is that it gives you the freedom to refactor drastically.
  Refactoring is the process of modifying operational code to make it perform better . Generally, "better" means "faster," but it can also mean "less memory," "less disk space," or "more compact." Regarding your environment and your project, no matter what refactoring means, it is vital to the long-term health of the program.
  In this example, "better" means both "faster" and "easier to maintain". Specifically, because the regular expressions used to verify Roman numerals are jerky and lengthy, the from _ roman () from\_roman()F R & lt O m _ R & lt O m A n- ( ) function I slower than desired, more complex. Now, you might be thinking, "Of course, regular expressions are stinky and long. Do I have other ways to verify whether any string is a Roman numeral?" The
  answer is: only convert 5000 numbers; why don't I create What about a lookup table? Realizing that there is no need to use regular expressions at all, this idea becomes even more ideal. While building a lookup table for converting integers to Roman numerals, you can also build a reverse lookup table for converting Roman numerals to integers. When you need to check whether any string is a valid Roman numeral, you will collect all valid Roman numerals. "Verification" is reduced to a simple dictionary lookup.
  The best part is that you already have a complete set of unit tests. More than half of the code in the module can be modified, and the unit test will remain unchanged. This means it can prove to you and others that the new code works as well as the original.
Insert picture description here
Insert picture description here
Insert picture description here

  It can be noticed that this is a function call, but there is no if ifThe i f statement wraps it. This is notif _ _ name _ _ = = ′ _ _ main _ _ ′ if\ \_\_name\_\_\ =='\_\_main\_\_'if __name__ ==__main__' Language block; it will be called when the module is imported (it is important to understand: the module will only beimported onceand then cached. If you import an imported module, nothing will happen. So this paragraph The code will onlyrunonthe first import).
Insert picture description here

  This is a clever program code...maybe too clever. The above defines to _ roman () to\_roman()T O _ R & lt O m A n- ( ) which lookup value in the lookup table and returns the result; function. Andbuild _ lookup _ tables () build\_lookup\_tables()B U I L D _ L O O K U P _ T A B L E S ( ) functionredefinetheto _ roman () to \ _romanThe t o _ r o m a n ( ) function is used for actual operation (like the example before adding the lookup table). Inbuild _ lookup _ tables () build\_lookup\_tables()b u i l d _ l o o k u p _ t a b l e s ( ) inside the function,to _ roman () to\_roman()The call to t o _ r o m a n ( ) will target the redefined version. Oncebuild _ lookup _ tables () build\_lookup\_tables()b u i l d _ l o o k u p _ t a b l e s ( ) function exits, and the redefined version will disappear — its definition is only inbuild _ lookup _ tables () build\_lookup\_tables( )b u i l d _ l o o k u p _ t a b l e s ( ) takes effect within the scope of the function.
Insert picture description here

  After performing the same boundary check as before, to _ roman () to\_roman()T O _ R & lt O m A n- ( ) function and returns only to find the appropriate value in the lookup table.
  Similarly,from _ roman () from\_roman()F R & lt O m _ R & lt O m A n- ( ) function is also shrunk to check some of the boundaries and one line of code. There are no more regular expressions. There is no longer a loop.
Insert picture description here

  Not only can it answer your questions, it also runs very fast! It seems that the speed has increased by 10 times. Of course, this comparison is not fair, because this version takes longer to import (when building the lookup table). However, since the import is only performed once, the startup cost can be changed from to _ roman () to\_roman()to_roman() f r o m _ r o m a n ( ) from\_roman() F R & lt O m _ R & lt O m A n- ( ) all diluted function calls. Since this test performs thousands of function calls (thousands of separate tests back and forth), the efficiency cost saved can be quickly improved!
Insert picture description here

  After studying this chapter, I also slightly realized the importance of unit testing... Indeed, if there are not enough test cases, code refactoring is a headache. I will be very entangled in the functions of the new code and the old code. Is it consistent...This will even affect the efficiency of refactoring the code. However, if you have a complete unit test, you can rest assured to refactor the code, because you can quickly verify the consistency of the new and old code functions.

4. Summary

  Unit testing is a powerful concept. If implemented correctly, it can not only reduce maintenance costs, but also increase the flexibility of long-term projects. But at the same time, it must be understood that unit testing is neither a panacea, nor a magic to solve problems, nor a silver bullet. Writing well-written test cases is very difficult, and ensuring that they are always up-to-date must be a discipline (especially when the customer requires critical bug fixes). Unit testing is not a substitute for functional testing, integration testing, or user endurance testing. But it is feasible and effective. After seeing its functions, you will be surprised that you have not used it before.
Insert picture description here

Guess you like

Origin blog.csdn.net/xiji333/article/details/110693138