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 PHPFeature 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 PHPFeature, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
25 | class PHPFeature implements FeatureInterface |
||
26 | { |
||
27 | |||
28 | /** |
||
29 | * RegEx pattern that matches the comparison string. |
||
30 | * |
||
31 | * @since 0.1.0 |
||
32 | * |
||
33 | * @var string |
||
34 | */ |
||
35 | const COMPARISON_PATTERN = '/^(?:(<=|lt|<|le|>=|gt|>|ge|=|==|eq|!=|<>|ne))([0-9].*)$/'; |
||
36 | |||
37 | /** |
||
38 | * Reference to the Configuration object. |
||
39 | * |
||
40 | * @since 0.1.0 |
||
41 | * |
||
42 | * @var ConfigInterface |
||
43 | */ |
||
44 | protected $config; |
||
45 | |||
46 | /** |
||
47 | * Reference to the Version object. |
||
48 | * |
||
49 | * @since 0.1.0 |
||
50 | * |
||
51 | * @var SemanticVersion |
||
52 | */ |
||
53 | protected $version; |
||
54 | |||
55 | /** |
||
56 | * Reference to the PHP releases. |
||
57 | * |
||
58 | * @since 0.2.4 |
||
59 | * |
||
60 | * @var PHPReleases |
||
61 | */ |
||
62 | protected $releases; |
||
63 | |||
64 | /** |
||
65 | * Instantiate a PHPFeature object. |
||
66 | * |
||
67 | * @since 0.1.0 |
||
68 | * |
||
69 | * @param SemanticVersion|string|int|null $phpVersion Version of PHP to check the features for. |
||
70 | * @param ConfigInterface|null $config Configuration that contains the known features. |
||
71 | * |
||
72 | * @throws RuntimeException If the PHP version could not be validated. |
||
73 | */ |
||
74 | 66 | public function __construct($phpVersion = null, ConfigInterface $config = null) |
|
98 | |||
99 | /** |
||
100 | * Check whether a feature or a collection of features is supported. |
||
101 | * |
||
102 | * Accepts either a string or an array of strings. Returns true if all the passed-in features are supported, or |
||
103 | * false if at least one of them is not. |
||
104 | * |
||
105 | * @since 0.1.0 |
||
106 | * |
||
107 | * @param string|array $features What features to check the support of. |
||
108 | * |
||
109 | * @return bool Whether the set of features as a whole is supported. |
||
110 | * @throws InvalidArgumentException If the wrong type of argument is passed in. |
||
111 | * @throws RuntimeException If a requirement could not be parsed. |
||
112 | */ |
||
113 | 23 | public function isSupported($features) |
|
136 | |||
137 | /** |
||
138 | * Get the minimum required version that supports all of the requested features. |
||
139 | * |
||
140 | * Accepts either a string or an array of strings. Returns a SemanticVersion object for the version number that is |
||
141 | * known to support all the passed-in features, or false if at least one of them is not supported by any known |
||
142 | * version. |
||
143 | * |
||
144 | * @since 0.2.0 |
||
145 | * |
||
146 | * @param string|array $features What features to check the support of. |
||
147 | * |
||
148 | * @return SemanticVersion|false SemanticVersion object for the version number that is known to support all the |
||
149 | * passed-in features, false if none. |
||
150 | * @throws InvalidArgumentException If the wrong type of argument is passed in. |
||
151 | * @throws RuntimeException If a requirement could not be parsed. |
||
152 | */ |
||
153 | 43 | public function getMinimumRequired($features) |
|
177 | |||
178 | /** |
||
179 | * Check whether a single feature is supported. |
||
180 | * |
||
181 | * @since 0.1.0 |
||
182 | * |
||
183 | * @param string $feature The feature to check. |
||
184 | * @param string|null $minimumRequired Optional. Minimum required version that supports all features. |
||
185 | * |
||
186 | * @return bool Whether the requested feature is supported. |
||
187 | * @throws RuntimeException If the requirement could not be parsed. |
||
188 | */ |
||
189 | 66 | protected function checkSupport($feature, &$minimumRequired = null) |
|
207 | |||
208 | /** |
||
209 | * Check whether a single requirement is met. |
||
210 | * |
||
211 | * @since 0.1.0 |
||
212 | * |
||
213 | * @param string $requirement A requirement that is composed of an operator and a version milestone. |
||
214 | * @param string|null $minimumRequired Optional. Minimum required version that supports all features. |
||
215 | * |
||
216 | * @return bool Whether the requirement is met. |
||
217 | * @throws RuntimeException If the requirement could not be parsed. |
||
218 | */ |
||
219 | 64 | protected function checkRequirement($requirement, &$minimumRequired = null) |
|
249 | |||
250 | /** |
||
251 | * Get the required version for a single requirement. |
||
252 | * |
||
253 | * @todo The entire algorithm is only an approximation. A 5.2 SemVer library is needed. |
||
254 | * |
||
255 | * @since 0.2.0 |
||
256 | * |
||
257 | * @param string $milestone A version milestone that is used to define the requirement. |
||
258 | * @param string $operator An operator that gets applied to the milestone. |
||
259 | * Possible values: '<=', 'lt', '<', 'le', '>=', 'gt', '>', 'ge', '=', '==', 'eq', '!=', |
||
260 | * '<>', 'ne' |
||
261 | * |
||
262 | * @return string Version string that meets a single requirement. |
||
263 | * @throws RuntimeException If the requirement could not be satisfied. |
||
264 | * @throws RuntimeException If the NotEqual is used. |
||
265 | */ |
||
266 | 42 | protected function getRequiredVersion($milestone, $operator) |
|
293 | |||
294 | /** |
||
295 | * Get a version greater than the milestone. |
||
296 | * |
||
297 | * @since 0.2.4 |
||
298 | * |
||
299 | * @param string $milestone A version milestone that is used to define the requirement. |
||
300 | * |
||
301 | * @return string Version number that meets the requirement. |
||
302 | * @throws RuntimeException If the requirement could not be satisfied. |
||
303 | */ |
||
304 | 4 | View Code Duplication | protected function getGreaterThanVersion($milestone) |
315 | |||
316 | /** |
||
317 | * Get a version lesser than the milestone. |
||
318 | * |
||
319 | * @since 0.2.4 |
||
320 | * |
||
321 | * @param string $milestone A version milestone that is used to define the requirement. |
||
322 | * |
||
323 | * @return string Version number that meets the requirement. |
||
324 | * @throws RuntimeException If the requirement could not be satisfied. |
||
325 | */ |
||
326 | 6 | View Code Duplication | protected function getLesserThanVersion($milestone) |
340 | |||
341 | /** |
||
342 | * Get a version greater or equal than the milestone. |
||
343 | * |
||
344 | * @since 0.2.4 |
||
345 | * |
||
346 | * @param string $milestone A version milestone that is used to define the requirement. |
||
347 | * |
||
348 | * @return string Version number that meets the requirement. |
||
349 | */ |
||
350 | 26 | View Code Duplication | protected function getGreaterEqualVersion($milestone) |
365 | |||
366 | /** |
||
367 | * Get a version lesser or equal than the milestone. |
||
368 | * |
||
369 | * @since 0.2.4 |
||
370 | * |
||
371 | * @param string $milestone A version milestone that is used to define the requirement. |
||
372 | * |
||
373 | * @return string Version number that meets the requirement. |
||
374 | */ |
||
375 | 6 | View Code Duplication | protected function getLesserEqualVersion($milestone) |
390 | } |
||
391 |
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.