Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like WC_Cart_Totals often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use WC_Cart_Totals, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
16 | class WC_Cart_Totals { |
||
17 | |||
18 | protected $calculate_tax = true; |
||
19 | protected $totals = null; |
||
20 | protected $coupons = array(); |
||
21 | protected $items = array(); |
||
22 | protected $fees = array(); |
||
23 | protected $shipping_lines = array(); |
||
24 | |||
25 | /** |
||
26 | * Get default blank set of props used per item. |
||
27 | * @return array |
||
28 | */ |
||
29 | private function get_default_item_props() { |
||
41 | |||
42 | /** |
||
43 | * Get default blank set of props used per coupon. |
||
44 | * @return array |
||
45 | */ |
||
46 | private function get_default_coupon_props() { |
||
53 | |||
54 | /** |
||
55 | * Get default blank set of props used per fee. |
||
56 | * @return array |
||
57 | */ |
||
58 | private function get_default_fee_props() { |
||
64 | |||
65 | /** |
||
66 | * Get default blank set of props used per shipping row. |
||
67 | * @return array |
||
68 | */ |
||
69 | private function get_default_shipping_props() { |
||
76 | |||
77 | /** |
||
78 | * Sets items and adds precision which lets us work with integers. |
||
79 | * @param array $items |
||
80 | */ |
||
81 | public function set_items( $items ) { |
||
95 | |||
96 | /** |
||
97 | * Set coupons. |
||
98 | * @param array $coupons |
||
99 | */ |
||
100 | public function set_coupons( $coupons ) { |
||
108 | |||
109 | /** |
||
110 | * Get fees. |
||
111 | * @return array |
||
112 | */ |
||
113 | public function get_coupons() { |
||
116 | |||
117 | /** |
||
118 | * Set fees. |
||
119 | * @param array $fees |
||
120 | */ |
||
121 | public function set_fees( $fees ) { |
||
131 | |||
132 | /** |
||
133 | * Should we calc tax? |
||
134 | * @param bool |
||
135 | */ |
||
136 | public function set_calculate_tax( $value ) { |
||
139 | |||
140 | /** |
||
141 | * Should we calc tax? |
||
142 | * @param bool |
||
143 | */ |
||
144 | public function get_calculate_tax() { |
||
147 | |||
148 | /** |
||
149 | * Subtotals are costs before discounts. |
||
150 | * |
||
151 | * To prevent rounding issues we need to work with the inclusive price where possible. |
||
152 | * otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would. |
||
153 | * be 8.325 leading to totals being 1p off. |
||
154 | * |
||
155 | * Pre tax coupons come off the price the customer thinks they are paying - tax is calculated. |
||
156 | * afterwards. |
||
157 | * |
||
158 | * e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that. |
||
159 | */ |
||
160 | private function calculate_item_subtotals() { |
||
180 | |||
181 | /** |
||
182 | * Totals are costs after discounts. |
||
183 | */ |
||
184 | public function calculate_item_totals() { |
||
205 | |||
206 | /** |
||
207 | * Calculate any fees taxes. |
||
208 | */ |
||
209 | private function calculate_fee_totals() { |
||
219 | |||
220 | /** |
||
221 | * Main cart totals. |
||
222 | */ |
||
223 | public function calculate_totals() { |
||
243 | |||
244 | /** |
||
245 | * Returns items and their totals. |
||
246 | * @return array |
||
247 | */ |
||
248 | public function get_item_totals() { |
||
251 | |||
252 | /** |
||
253 | * Get tax rates for an item. Caches rates in class to avoid multiple look ups. |
||
254 | * @param object $item |
||
255 | * @return array of taxes |
||
256 | */ |
||
257 | private function get_item_tax_rates( $item ) { |
||
261 | |||
262 | /** |
||
263 | * Sort items by the subtotal. |
||
264 | */ |
||
265 | private function sort_items_callback( $a, $b ) { |
||
273 | |||
274 | /** |
||
275 | * Get the subtotal for all items. |
||
276 | * |
||
277 | * @param boolean $inc_tax Should tax also be included in the subtotal? |
||
278 | * @return float |
||
279 | */ |
||
280 | View Code Duplication | public function get_items_subtotal( $inc_tax = true ) { |
|
289 | |||
290 | /** |
||
291 | * Get the total for all items. |
||
292 | * |
||
293 | * @param boolean $inc_tax Should tax also be included in the subtotal? |
||
294 | * @return float |
||
295 | */ |
||
296 | View Code Duplication | public function get_items_total( $inc_tax = true ) { |
|
305 | |||
306 | /** |
||
307 | * Get the total discount amount. |
||
308 | * @return float |
||
309 | */ |
||
310 | public function get_discount_total() { |
||
313 | |||
314 | /** |
||
315 | * Get the total discount amount. |
||
316 | * @return float |
||
317 | */ |
||
318 | public function get_discount_total_tax() { |
||
321 | |||
322 | /** |
||
323 | * Should discounts be applied sequentially? |
||
324 | * @return bool |
||
325 | */ |
||
326 | private function calc_discounts_sequentially() { |
||
329 | |||
330 | /** |
||
331 | * Get an items price to discount (undiscounted price). |
||
332 | * @param object $item |
||
333 | * @return float |
||
334 | */ |
||
335 | private function get_undiscounted_price( $item ) { |
||
342 | |||
343 | /** |
||
344 | * Get an individual items price after discounts are applied. |
||
345 | * @param object $item |
||
346 | * @return float |
||
347 | */ |
||
348 | public function get_discounted_price( $item ) { |
||
405 | |||
406 | /** |
||
407 | * Only ran if woocommerce_adjust_non_base_location_prices is true. |
||
408 | * |
||
409 | * If the customer is outside of the base location, this removes the base taxes. |
||
410 | * |
||
411 | * Pre 2.7, this was on by default. From 2.7 onwards this is off by default meaning |
||
412 | * that if a product costs 10 including tax, all users will pay 10 regardless of location and taxes. This was experiemental from 2.4.7. |
||
413 | */ |
||
414 | private function adjust_non_base_location_price( $item ) { |
||
429 | |||
430 | /** |
||
431 | * Get the total for all items. |
||
432 | * |
||
433 | * @param boolean $inc_tax Should tax also be included in the subtotal? |
||
434 | * @return float |
||
435 | */ |
||
436 | View Code Duplication | public function get_fees_total( $inc_tax = true ) { |
|
445 | |||
446 | /** |
||
447 | * Get all tax rows for a set of items and shipping methods. |
||
448 | * @return array |
||
449 | */ |
||
450 | public function get_merged_taxes() { |
||
475 | |||
476 | /** |
||
477 | * Get taxes. |
||
478 | * @return array |
||
479 | */ |
||
480 | public function get_taxes() { |
||
483 | |||
484 | /** |
||
485 | * Get tax total. |
||
486 | * @return float |
||
487 | */ |
||
488 | public function get_tax_total() { |
||
491 | |||
492 | /** |
||
493 | * Get shipping tax total. |
||
494 | * @return float |
||
495 | */ |
||
496 | public function get_shipping_tax_total() { |
||
499 | |||
500 | /** |
||
501 | * Get grand total. |
||
502 | * @return float |
||
503 | */ |
||
504 | public function get_total() { |
||
507 | |||
508 | /** |
||
509 | * Get the total for all items. |
||
510 | * |
||
511 | * @param boolean $inc_tax Should tax also be included in the subtotal? |
||
512 | * @return float |
||
513 | */ |
||
514 | View Code Duplication | public function get_shipping_total( $inc_tax = true ) { |
|
523 | |||
524 | /** |
||
525 | * Set shipping lines. |
||
526 | * @param array |
||
527 | */ |
||
528 | public function set_shipping( $shipping_objects ) { |
||
542 | } |
||
543 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.