📜 ⬆️ ⬇️

Basics of dealing with implicit code duplication

Code with the same structure in two or more places is a sure sign of the need for refactoring. If you need to change something in one place, then, most likely, you should also do the same in other places. But there is a probability close to 100% not to find these places or simply forget for them.

Most understand that repeated repetition of the code (or copy-paste) in the example below is evil:

// //    -  // void DrawCircle(int x, int y, int radius) { ///       (x, y)  radius } … DrawCircle(getScreenMetrics().width / 2, getScreenMetrics().height / 2, 100500); DrawCircle(getScreenMetrics().width / 2, getScreenMetrics().height / 2, 100600); 


The most obvious way out is to put the coordinates of the center of the circle in separate variables:
')
 ScreenMetrics metrics = getScreenMetrics(); int centerX = metrics.width / 2; int centerY = metrics.height / 2; DrawCircle(centerX, centerY, 100); DrawCircle(centerX, centerY, 200); 


In the example above, duplication is eliminated in two seconds , because the example is sucked from the finger , because the expressions defining the coordinates of the center of the circles completely coincide:

 getScreenMetrics().width / 2, getScreenMetrics().height / 2 


At the same time, not all developers are able to recognize and eliminate implicit code duplication.

Take for example a kind of mobile application that interacts with the server API and
Allows an authorized user to view and upload photos:

 HttpResponse login(String email, String password) { HttpRequest request = new HttpRequest(); request.setMethod(“GET”); request.setContentType("application/x-www-form-urlencoded"); String parameters = "?login=" + login + "&password=" + password; String uri = "https://mycoolsite.com/api/login" + parameters; request.setUrl(uri); return request.execute(); } ... HttpResponse getPhotos(int userId) { String uri = "https://mycoolsite.com/api/get_photos?user_id=" + user_id; HttpRequest request = new HttpRequest(); request.setMethod("GET"); request.setUrl(uri); request.setContentType("application/x-www-form-urlencoded"); return request.execute(); } … bool uploadPhoto(Bitmap photo, int user_id) { HttpRequest request = new HttpRequest(); HttpBody body = convertBitmapToHttpBody(photo); request.setUrl("https://mycoolsite.com/api/upload_photo?user_id=" + user_id); request.setMethod("POST"); request.setContentType(“multipart/form-data”); request.setHttpBody(body); HttpResponse response = request.execute(); return (response.getStatusCode()== 200); } 


... and another 100,500 similar methods totaling the same number of API calls.

What happens if mycoolsite.com moves to awesome.net? We'll have to look for the entire project calls request.execute () and edit the line. And somewhere you will surely forget to correct the URL or just be sealed. As a result, spend a lot of time, send a “corrected” build to the customer closer to night, but the next day SUDDENLY get a bug report: “Photos upload stopped working”, for example.

If you do not write the code immediately "in the forehead," and go to smoke, stop and think a little, then you will notice that all three methods above do almost the same thing:


First of all, you should put the common code into separate methods:

 public HttpRequest createGetRequest() { HttpRequest request = new HttpRequest(); request.setMethod("GET"); request.setContentType("application/x-www-form-urlencoded"); return request; } public HttpRequest createPostRequest() { HttpRequest request = new HttpRequest() request.setMethod("POST"); request.setContentType("multipart/form-data"); return request; } 


Special attention should be paid to the transfer of parameters and the formation of the request URI.
The initial version of the code uses string concatenation:

 String uri = "https://mycoolsite.com/api/get_photos?user_id=" + user_id; 


That is, for each request you need to form a URI string, and this is also a duplication of the code.
and is fraught with problems when making changes. API root address and parameter adding
The URI should be kept in one place.

 public static final String API_BASE_URL = "https://mycoolsite.com/api/"; 


Before forming the request URI, we will collect all the parameters and their values ​​in the Map:

 Map params = new HashMap<String, String>(); ... public void addParam(Map<String, String> params, String param, String value) { params.put(param, value); } //       public void addParam(Map<String, String> params, String param, int value) { params.put(param, String.valueOf(value)); } 


Now, to generate the full URI of the request, we will write the following code:

 public String getUri(String path, Map<String, String> params) { StringBuilder query = new StringBuilder("?"); Iterator iterator = params.allKeys().iterator(); while (iterator.hasNext()) { String paramName = iterator.next(); query.add(paramName + "=" + params.get(paramName)); if (iterator.hasNext()) { query.add("&"); } } return API_BASE_URL + path + result.toString(); } 


So, to execute the request to the server, we need:



 HttpRequest request = createGetRequest(); //  createPostRequest,     




 Map params = new HashMap<String, String>(); ... addParam(params, "user_id", userId); // ,  userId   String uri = getUri("get_photos"); //   : https://mycoolsite.com/api/get_photos?user_id=%user_id% 


Now you can make a request to the server, get an answer, and then do anything you like with it:

 request.setUrl(uri); HttpResponse response = request.execute(); 


Now we put all this stuff into a separate class. We will also inherit this class from
HttpRequest, because in some cases we need to use its methods.

We get about the following code:

 public class MyRequest extends HttRrequest { private static final String API_BASE_URL = "https://mycoolsite.com/api/"; private Map<String, String> mParameters; private MyRequest() { super(); mParameters = new HashMap<String, String>(); } public static MyRequest createGetRequest() { MyRequest request = new MyRequest(); request.setMethod("GET"); request.setContentType("application/x-www-form-urlencoded"); return request; } public static MyRequest createPostRequest() { MyRequest request = new MyRequest() request.setMethod("POST"); request.setContentType("multipart/form-data"); return request; } public void addParam(String name, String value) { mParameters.put(name, value); } public void addParam(String name, int value) { addParam(name, String.valueOf(value)); } public HttpResponse send(String path) { String uri = API_BASE_URL + path getParametersString(); setUrl(uri); execute(); } private String getParametersString() { StringBuilder result = new StringBuilder("?"); Iterator iterator = mParameters.allKeys().iterator(); while (iterator.hasNext()) { String paramName = iterator.next(); result.add(paramName + "=" + mParameters.get(paramName)); if (iterator.hasNext()) { result.add("&"); } } return result.toString(); } } 


Now the code of the API calls will look like this:

 HttpResponse login(String login, String password) { MyRequest request = MyRequest.createGetRequest(); request.addParam("login", login); request.addParam("password", password); return request.send("login"); } HttpResponse getPhotos(int userId) { MyRequest request = MyRequest.createGetRequest(); request.addParam("user_id", userId); return request.send("get_photos"); } HttpResponse uploadPhoto(Bitmap photo, int userId) { MyRequest request = MyRequest.createPostRequest(); request.addParam(“user_id”, userId); request.setHttpBody(convertBitmapToHttpBody(photo)); return request.send(“upload_photo”); } 


As you can see, the login, getPhotos and uploadPhoto methods have become shorter, simpler and clearer.

This was an example of non-trivial, multi-step refactoring. As a result, a new class MyRequest appeared, which is a wrapper over the system HttpRequest, with a preference and courtesans with the ability to set query parameters and a couple of other pleasant trifles. You can also notice how the methods of specific API calls have become smaller, simpler and clearer. Add a new challenge is almost no difficulty.

This refactoring takes a couple of hours more time than adding another API call using copy-paste method. But in the future, a few minutes, not days, will be spent on making changes and debugging.

Naturally, the resulting code is far from perfect, but still it is much better than before refactoring, and this is already a big
a step forward, with a little blood. In the future, if there is time, this code can and should be improved.

Moral: noticed duplication - eliminate it immediately . Save a lot of time and nerves in the future.
At the same time, eliminating the "complex" duplication, take the time to re-design a small part of the project,
do not write the code immediately.

Read more in the book on Fowler refactoring .

I would be glad to constructive criticism, suggestions and additions to the article. Thanks for attention!

Source: https://habr.com/ru/post/197932/


All Articles