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 JsonDecodeReader 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 JsonDecodeReader, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
20 | final class JsonDecodeReader implements JsonReadable |
||
21 | { |
||
22 | /** |
||
23 | * A stack representing the next element to be consumed |
||
24 | * |
||
25 | * @var array |
||
26 | */ |
||
27 | private $stack = []; |
||
28 | |||
29 | /** |
||
30 | * An array of types that map to the position in the stack |
||
31 | * |
||
32 | * @var array |
||
33 | */ |
||
34 | private $stackTypes = []; |
||
35 | |||
36 | /** |
||
37 | * The current size of the stack |
||
38 | * |
||
39 | * @var int |
||
40 | */ |
||
41 | private $stackSize = 0; |
||
42 | |||
43 | /** |
||
44 | * A cache of the current [@see JsonToken]. This should get nulled out |
||
45 | * whenever a new token should be returned with the subsequent call |
||
46 | * to [@see JsonDecodeReader::peek] |
||
47 | * |
||
48 | * @var |
||
49 | */ |
||
50 | private $currentToken; |
||
51 | |||
52 | /** |
||
53 | * Constructor |
||
54 | * |
||
55 | * @param string $json |
||
56 | */ |
||
57 | 69 | public function __construct(string $json) |
|
61 | |||
62 | /** |
||
63 | * Consumes the next token and asserts it's the beginning of a new array |
||
64 | * |
||
65 | * @return void |
||
66 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not BEGIN_ARRAY |
||
67 | */ |
||
68 | 15 | public function beginArray(): void |
|
79 | |||
80 | /** |
||
81 | * Consumes the next token and asserts it's the end of an array |
||
82 | * |
||
83 | * @return void |
||
84 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not END_ARRAY |
||
85 | */ |
||
86 | 5 | public function endArray(): void |
|
96 | |||
97 | /** |
||
98 | * Consumes the next token and asserts it's the beginning of a new object |
||
99 | * |
||
100 | * @return void |
||
101 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not BEGIN_OBJECT |
||
102 | */ |
||
103 | 17 | public function beginObject(): void |
|
113 | |||
114 | /** |
||
115 | * Consumes the next token and asserts it's the end of an object |
||
116 | * |
||
117 | * @return void |
||
118 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not END_OBJECT |
||
119 | */ |
||
120 | 4 | public function endObject(): void |
|
130 | |||
131 | /** |
||
132 | * Returns true if the array or object has another element |
||
133 | * |
||
134 | * If the current scope is not an array or object, this returns false |
||
135 | * |
||
136 | * @return bool |
||
137 | */ |
||
138 | 4 | public function hasNext(): bool |
|
144 | |||
145 | /** |
||
146 | * Consumes the value of the next token, asserts it's a boolean and returns it |
||
147 | * |
||
148 | * @return bool |
||
149 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not BOOLEAN |
||
150 | */ |
||
151 | 6 | View Code Duplication | public function nextBoolean(): bool |
161 | |||
162 | /** |
||
163 | * Consumes the value of the next token, asserts it's a double and returns it |
||
164 | * |
||
165 | * @return double |
||
166 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NUMBER |
||
167 | */ |
||
168 | 3 | View Code Duplication | public function nextDouble(): float |
178 | |||
179 | /** |
||
180 | * Consumes the value of the next token, asserts it's an int and returns it |
||
181 | * |
||
182 | * @return int |
||
183 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NUMBER |
||
184 | */ |
||
185 | 7 | View Code Duplication | public function nextInteger(): int |
195 | |||
196 | /** |
||
197 | * Consumes the value of the next token, asserts it's a string and returns it |
||
198 | * |
||
199 | * @return string |
||
200 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NAME or STRING |
||
201 | */ |
||
202 | 13 | View Code Duplication | public function nextString(): string |
217 | |||
218 | /** |
||
219 | * Consumes the value of the next token and asserts it's null |
||
220 | * |
||
221 | * @return null |
||
222 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NAME or NULL |
||
223 | */ |
||
224 | 2 | View Code Duplication | public function nextNull() |
236 | |||
237 | /** |
||
238 | * Consumes the next name and returns it |
||
239 | * |
||
240 | * @return string |
||
241 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NAME |
||
242 | */ |
||
243 | 9 | View Code Duplication | public function nextName(): string |
261 | |||
262 | /** |
||
263 | * Returns an enum representing the type of the next token without consuming it |
||
264 | * |
||
265 | * @return string |
||
266 | */ |
||
267 | 69 | public function peek(): string |
|
326 | |||
327 | /** |
||
328 | * Skip the next value. If the next value is an object or array, all children will |
||
329 | * also be skipped. |
||
330 | * |
||
331 | * @return void |
||
332 | */ |
||
333 | 1 | public function skipValue(): void |
|
337 | |||
338 | /** |
||
339 | * Push an element onto the stack |
||
340 | * |
||
341 | * @param mixed $element |
||
342 | * @param string $type |
||
343 | */ |
||
344 | 69 | View Code Duplication | private function push($element, $type = null): void |
355 | |||
356 | /** |
||
357 | * Pop the last element off the stack and return it |
||
358 | * |
||
359 | * @return mixed |
||
360 | */ |
||
361 | 45 | View Code Duplication | private function pop() |
369 | } |
||
370 |
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.