Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
We all know to keep it simple, right?
I've seen complexity being measured as the number of interactions between systems, and I guess that's a very good place to start. Aside from gut feel though, what other (preferably more objective) methods can be used to determine the level of complexity of a particular design or piece of software?
What are YOUR favorite rules or heuristics?
Here are mine:
1) How hard is it to explain to someone who understands the problem but hasn't thought about the solution? If I explain the problem to someone in the hall (who probably already understands the problem if they're in the hall) and can explain the solution, then it's not too complicated. If it takes over an hour, chances are good the solution's overengineered.
2) How deep in the nested functions do you have to go? If I have an object which requires a property held by an object held by another object, then chances are good that what I'm trying to do is too far removed from the object itself. Those situations become problematic when trying to make objects thread-safe, because there'd be many objects of varying depths from your current position to lock.
3) Are you trying to solve problems that have already been solved before? Not every problem is new (and some would argue that none really are). Is there an existing pattern or group of patterns you can use? If you can't, why not? It's all good to make your own new solutions, and I'm all for it, but sometimes people have already answered the problem. I'm not going to rewrite STL (though I tried, at one point), because the solution already exists and it's a good one.
Complexity can be estimated with the coupling and how cohesive are all your objects. If something is have too much coupling or is not enough cohesive, than the design will start to be more complex.
When I attended the Complex Systems Modeling workshop at the New England Complex Systems Institute (http://necsi.org/), one of the measures that they used was the number of system states.
For example if you have two nodes, which interact, A and B, and each of these can be 0 or 1, your possible states are:
A B
0 0
1 0
0 1
1 1
Thus a system of only 1 interaction between binary components can actually result in 4 different states. The point being that the complexity of the system does not necessarily increase linearly as the number of interactions increases.
good measures can be also number of files, number of places where configuration is stored, order of compilation on some languages.
Examples:
.- properties files, database configuration, xml files to hold related information.
.- tens of thousands of classes with interfaces, and database mappings
.- a extremely long and complicated build file (build.xml, Makefile, others..
If your app is built, you can measure it in terms of time (how long a particular task would take to execute) or computations (how much code is executed each time the task is run).
If you just have designs, then you can look at how many components of your design are needed to run a given task, or to run an average task. For example, if you use MVC as your design pattern, then you have at least 3 components touched for the majority of tasks, but depending on your implementation of the design, you may end up with dozens of components (a cache in addition to the 3 layers, for example).
Finally something LOC can actually help measure? :)
i think complexity is best seen as the number of things that need to interact.
A complex design would have n tiers whereas a simple design would have only two.
Complexity is needed to work around issues that simplicity cannot overcome, so it is not always going to be a problem.
There is a problem in defining complexity in general as complexity usually has a task associated with it.
Something may be complex to understand, but simple to look at (very terse code for example)
The number of interactions getting this web page from the server to your computer is very complex, but the abstraction of the http protocol is very simple.
So having a task in mind (e.g. maintenance) before selecting a measure may make it more useful. (i.e. adding a config file and logging to an app increases its objective complexity [yeah, only a little bit sure], but simplifies maintenance).
There are formal metrics. Read up on Cyclomatic Complexity, for example.
Edit.
Also, look at Function Points. They give you a non-gut-feel quantitative measurement of system complexity.
Related
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 years ago.
Improve this question
When writing a program, my understanding as a hobbyist programmer is, there are three ways to accomplish most of the things:
create loops
create and use function
create and use object
I am using javascript here to ask my question since I took learning on it about 2-3 weeks before. It is somewhat strange compared to what I was used to in python or MatLab in University but that's not the point. I often think what should be the good choice among three in particular application so I wanted to know your suggestion.
I wanted to create a list of array to subsequent use for plotting. The program is supposed to take coefficients of the equation, incremental step and the boundary for x-values. Below is the code (Sorry if I missed something below when changing to fit SO, but it was working some moments before!):
function array_creator(input_coeff,inc, boundary){
var bound=boundary||[0,1];
var eqn_deg=input_coeff.length-1;
var increment=inc;
var x_init=bound[0];
var y_val=0;
var graph_array=[];
while (x_init<=bound[1]){
for(var i=0;i<input_coeff.length;i++){
y_val=y_val+input_coeff[i]*Math.pow(x_init,eqn_deg);
eqn_deg--;
}
new_arr=[x_init,y_val];
eqn_deg=input_coeff.length-1;
y_val=0;
graph_array.push(new_arr);
x_init=x_init+increment;
}
return graph_array;
}
In above code, I have one nested loop which goes inside the while but I am used to writing codes that goes more than 3-4 deep in nesting and I cannot dig my own program a week after. So my question is, when should I know that it is time to implement separate function rather than having nesting or know the time to create an object. What are the gains and losses of breaking one big looped function into several function in terms of clarity and efficiency? At what point the creating of object becomes essential or is it just when I have to re-use the same code again.
When the only tool you have is hammer, everything looks like nail. When I started learning python after MatLab, I was so impressed by OO approach that I used to create classes in every situation whether needed or not. I think many SO newbies will be glad to find some systematic approach on this programming fundamentals.
Personally, as far as loops go I have a hard cutoff of three. If I ever hit three nested loops (not counting if/else conditions or try/catch conditions) then I know it's time to break it up into separate functions.
As far as tradeoffs, so long as the function you're running is run many times in quick succession (as in the lower tiers of a loop) there shouldn't be really any performance loss. There is always a slight overhead associated with making a function call, but luckily computers are really smart and they have these things call temporal caches where a cache is an area of extremely fast memory (read: SRAM). That will recognize and load your function call into the cache. Since accessing things already in the cache is effectively free (read times of a few ns) you won't really pay any performance loss for those extra function calls.
The usage of classes is very language dependant though. In javascript, everything is already an object, so you really shouldn't worry about wrapping functions in classes, though again there will be a slight overhead. For languages like Java however, you should endeavour to make a large number of small classes. The JVM is extremely optimized for talking between multiple classes and the JIT compiler shouldn't load up any of the extra "goo" involved in the classes unless you really need it.
In general though, performance is not what you should be making most of your decisions based on (performance is very 80/20, and all you usually need is the 80 of not doing anything overtly silly.) You should really try to follow a pattern that makes your code as readable as possible to other developers. It's pretty hard to define a hard and fast rule as there are many camps on the subject. In general though, for a starting programmer my advice would be to look at a LOT of code and try to understand what's happening. Try to rewrite portions of code in a more readable format if you can. There's enough open source code on Github that it should be pretty easy to do.
Also good programming practices have always been opinions, it's just that people remarkably agree sometimes.
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 8 years ago.
Improve this question
I am trying to get a couple team-members on to the OOP mindset, who currently think in terms of procedural programming.
However I am having a hard time putting into terms the "why" all this is good, and "why" they should want to benefit from it.
They use a different language than I do, and I am lacking the communication skills to explain this to them in a way that makes them "want" to learn the OOP way of doing things.
What are some good language independent books, articles, or arguments anyone can give or point to?
OOP is good for a multi-developer team because it easily allows abstraction, encapsulation, inheritance and polymorphism. These are the big buzz words of OOP and they are the big buzz words for good reasons.
Abstraction: Allows other members of your team to use code that you write without having to understand the implementation details. This reduces the amount of necessary communication. Think of The Mythical Man Month wherein it is detailed that communication is one of the highest costs facing a development team.
Encapsulation: Allows you to change your implementation details without impacting users of your code. As such, it reduces code maintenance costs.
Inheritance: Allows your team to reuse and extend your implementations with reduced costs.
Polymorphism: Allows your team to use different implementations of a given abstraction. If your team is writing code to read and parse data from a Stream, because of polymorphism it can now work with FileStreams, MemoryStreams and PigeonStreams seamlessly and with significantly reduced costs.
OOP is not a holy grail. It is inappropriate for some teams because the costs of using it could be higher than the costs of not using it. For example, if you try to design for polymorphism but never have multiple implementations of a given abstraction then you have probably increased your costs.
Always give examples.
Take a bit of their code you think is bad. Re-write it to be better. Explain why it is better. Your colleagues will either agree or disagree.
Nobody uses (or should use) techniques because they're good techniques, they (should) use them because they produce good results. The advantages of very simple use of classes and objects are usually fairly easy to see, for instance when you have an array of objects with n properties instead of n arrays, one for each field you care about, and so on.
Comparing procedural to OOP, the biggest winner by far is encapsulation. OOP doesn't mean that you get encapsulation automatically, but the process of doing it is free compared with procedural code.
Abstraction helps manage the complexity of an application: only the information that's required is exposed.
There are many ways to go about this: OOP is not the only discipline to promote this strategy.
Of course, it is not because one claims to do OOP that one builds an application without abundant "abstraction leaks" thereby defeating the strategy...
I have a bit strange thought. I don't know but there probably some areas exist where OOP is unnecessary or even bad (very-very IMHO: javascript programming).
You and your team probably work in one of these areas. In other case you'd failed many years ago due to teams which use oop and all its benefits (like different frameworks, UML and so on) would simply do their job more efficiently.
I mean that if you still work well without oop then, maybe, just leave it.
The killer phrase: With OOP you can model the world "as it is" *cough*.
OOP didn't make sense to me until I was working on an application that connected to two different databases. I needed a function called getEmployeeName() for both databases. I decided to create two objects, one for each database, to encapsulate the functions that ran against each one (there were no functions that ran against both simultaneously). Not the epitomy of OOP, but a good start for me.
Most of my code is still procedural, but I'm more aware of situations where objects would make sense in my code. I'm not of the mindset that everything needs to be one way or the other.
Re-use of existing code through hierarchies.
The killer argument is IMHO that it takes much less time to re-design your code. Here is a similar question explaining why.
Having the ability to pass an entire object around that has a bunch of methods/functions you can call using that object. For example, let's say you have want to pass a message around you only need to pass one object and everyone who gets that object will have access to all it's functions.
Also, you can declare some objects' functions as public and some as private. There is also the concept of a friend function where only objects that are related through OO hierarchies have access to their friend's functions.
Objects help keep functions near the data they use and encapsulates it all into one entity that can be easily passed around.
I remember when I was in DSA I was like wtf O(n) and wondering where would I use it other than in grad school or if you're not a PhD like Bloch. Somehow uses for it does pop up in business analysis, so I was wondering when have you guys had to call up your Big O skills to see how to write an algorithm, which data structure did you use to fit or whether you had to actually create a new ds (like your own implementation of a splay tree or trie).
Understanding Data Structures has been fundamental to many of the projects I've worked on, and that goes beyond the ten minute song 'n dance one does when asked such a question in an interview situation.
Granted that modern environments with all sorts of collection classes can make light work of storing and accessing large amounts of data, but having an understanding that a particular problem is best solved with a particular data structure can be a great timesaver. And by "timesaver" I mean "the difference between something working and not working".
Honestly, being able to answer that stuff is my biggest criterion for taking interviewees seriously in an interview. Knowing how basic data structures work, basic O(n) analysis, and some light theory is really crucial to being able to write large applications successfully.
It's important in the interview because it's important in the job. I've worked with techs in the past that were self taught, without taking the data structures course or reading a data structures book, and their code is occasionally bad in ways they should have seen coming.
If you don't know that n2 is going to run slowly compared to n log n, you've got more to learn.
As far as the later half of the data structures courses, it isn't generally applicable to most tech jobs, but if you ever do wind up needing it, you'll wish you had paid more attention.
Big-O notation is one of the basic notations used when describing algorithms implemented by a particular library. For example, all documentation on STL that I've seen describes various operations in terms of big-O, so naturally you have to e.g. understand the difference between O(1), O(log n) and O(n) to understand the implications of your choice of STL containers and algorithms. MSDN also does that for .NET classes, and IIRC Java documentation does that for standard Java classes. So, I'd say that knowing the notation is pretty much a requirement for understanding documentation of most popular frameworks out there.
Sure (even though I'm a humble MS in EE -- no PhD, no CS, differently from my colleague Joshua Block), I write a lot of stuff that needs to be highly scalable (or components that may need to be reused in highly scalable apps), so big-O considerations are most always at work in my design (and it's not hard to take them into account). The data structures I use are almost always from Python's simple but rich supply (which I did lend a hand developing;-), rarely is a totally custom one needed (rather than building on top of list, dict, etc); but when it does happen (e.g. the bitvectors in my open source project gmpy), no big deal.
I was able to use B-Trees right when I learned about them in algorithm class (that was about 15 years ago when there were much less open source implementations available). And even later the knowledge about the differences of e. g. container classes came in handy...
Absolutely: even though stacks, queues, etc. are pretty straightforward, it helps to have been introduced to them in a disciplined fashion.
B-Tree's and more advanced sorting are a bit more difficult so learning them early was a big benefit and I have indeed had to implement each of them at various points.
Finally, I created an algorithm for single-connected components a few years back that was significantly better than the one our signal-processing team was using but I couldn't convince them that it was better until I could show that it was O(n) complexity rather than O(nlogn).
...just to name a few examples.
Of course, if you are content to remain a CRUD-system hacker with no real desire to do more than collect a paycheck, then it may not be necessary...
I found my knowledge of data structures very useful when I needed to implement a customizable event-driven system about ten years ago. That's the biggie, but I use that sort of knowledge fairly frequently in lesser ways.
For me, knowing the exact algorithms has been... nice as background knowledge. However, the thing that's been the most useful is the more general background of having to pay attention to how different pieces of an algorithm interact. For instance, there can be places in code where moving one piece of code (ie, outside a loop) can make a huge difference in both time and space.
Its less of the specific knowledge the course taught and, rather, more that it acted like several years of experience. The course took something that might take years to encounter (have drilled into you) all the variations of in pure "real world experience" and condensed it.
The title of your question asks about data structures and algorithms, but the body of your question focuses on complexity analysis, so I'll focus on that too:
There are lots of programming jobs where being able to do complexity analysis is at least occasionally useful. See What career can I hope for if I like algorithms? for some examples of these.
I can think of several instances in my career where either I or a co-worker have discovered a a piece of code where the (usually time, sometimes space) complexity was higher that it should have been. eg: something that was quadratic or cubic when it could have been linear or nlog(n). Such code would work fine when given small inputs, but on larger inputs would quickly become really slow or consume all available memory. Knowing alternative algorithms and data structures, their complexities, and also how to analyze the complexity to build new algorithms is vital in being able to correct these problems (or avoid them in the first place).
Networking is all I've used it: in an implementation of traveling salesman.
Unfortunately I do a lot of "line of business" and "forms over data" apps, so most problems I work on can be solved by hammering together arrays, linked lists, and hash tables. However, I've had the chance to work my data structures magic here and there:
Due to weird complex business rules, I worked on an application which used a custom thread pool implemented as a leftist-heap.
My dev team struggled to write a complex multithreaded app. It was plagued with race conditions, dead locks, and lousy performance due to very fine-grained locking. We re-worked the code to share state between threads, opting to write a very light-weight wrapper to facilitate message passing. Next, we converting our linked lists and hash tables to immutable stacks and immutable style and immutable red-black trees, we had no more problems with thread safety or performance. The resulting code was immaculate and surprisingly readable.
Frequently, a business rules engine requires you to roll your own state machine, which is very naturally modelled as a graph where vertexes and states and edges are transitions between states.
If for no other reasons, I'm glad I took the time to readable about data structures and algorithms simply to be able picture novel problems a little differently, especially combinatorial problems and graph problems. Graph theory is no longer a synonym for "scary".
Closed. This question needs to be more focused. It is not currently accepting answers.
Closed 4 years ago.
Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
Functional Decomposition, what is it useful for and what are its pros/cons? Where are there some worked examples of how it is used?
Functional Decomposition is the process of taking a complex process and breaking it down into its smaller, simpler parts.
For instance, think about using an ATM. You could decompose the process into:
Walk up to the ATM
Insert your bank card
Enter your pin
well...you get the point.
You can think of programming the same way. Think of the software running that ATM:
Code for reading the card
PIN verification
Transfer Processing
Each of which can be broken down further. Once you've reached the most decomposed pieces of a subsystem, you can think about how to start coding those pieces. You then compose those small parts into the greater whole. Check out this Wikipedia Article:
Decomposition (programming)
The benefit of functional decomposition is that once you start coding, you are working on the simplest components you can possibly work with for your application. Therefore developing and testing those components becomes much easier (not to mention you are better able to architect your code and project to fit your needs).
The obvious downside is the time investment. To perform functional decomposition on a complex system takes more than a trivial amount of time BEFORE coding begins.
Personally, I think that amount of time is well worth it.
It's the same as WorkBreakDown Structures (WBS), mindMapping and top down development - basically breaking a large problem into smaller, more comprehensible sub-parts.
Pros
allows for a proactive approach to programming (resiting the urge to code)
helps identify the complex and/or risk areas of a project (in the ATM example, security is probably the more complex component)
helps identify ALL components of a project - the #1 cause of project/code failure (via Capers Jones) is missing pieces - things not thought of until late in the project (gee, I didn't realize I had to check the person's balance prior to handing out the $)
allows for decoupling of components for better programming, sharing of code and distribution of work
Cons - there are no real CONS in doing a decomposition, however there are some common mistakes
not breaking down far enough or breaking down to far - each person needs to determine the happy level of detail needed to provide them with the insight to the component without overdoing it (don't break down into programming lines of code...)
not using pre-existing patterns/code modules into consideration (rework)
not reviewing with clients to ensure the scope is correct
not using the breakdown when actually coding (like designing a house than forgetting about the plan and just starting to nail some boards together)
Here's an example: your C compiler.
First there's the preprocessor: it handles #include and #define and all the macros. You give it a file name and some options and it returns a really long string. Let's call this function preprocess(filename).
Then there's the lexical analyzer. It takes a string and breaks it into tokens. Call it lex(string). The parser takes tokens and turns them into a tree, call it parse(tokens). Then there's a function for converting a tree to a DAG of blocks, call it dag(tree). Call the code emitter emit(dag), which takes a DAG of blocks and spits out assembler.
The compiler is then:
emit(dag(parse(lex(preprocess(filename)))));
We've decomposed a big, difficult to understand function (the compile function) into a bunch of smaller, easier to understand functions. You don't have to do it as a pipeline, you could write your program as:
process_data(parse_input(), parse_config())
This is more typical; compilers are fairly deep programs, most programs are broad by comparison.
Functional decomposition is a way of breaking down the complex problem into simpler problems based on the tasks that need to be performed rather than the the data relationships. This term is usually associated with the older procedure-oriented design.
A short description about the difference between procedure-oriented and object-oriented design.
Functional decomposition is helpful prior to creating functional requirements documents. If you need software for something, functional decomposition answers the question "What are the functions this software must provide". Decomposing is needed to define fine-grain functions. "I need software for energy efficiency measurement" is too general. That's why we break this into smaller pieces until the point where we clearly understand all the functions the systems need to provide. This can be later used as a checklist for completeness of a system.
A functional requirements document (FD) is basically a textual representation of functional decomposition. Coding directly from the FD may be ok for procedural languages, but it is not good enough for object-oriented solutions, because it doesn't identify objects. Neither is good for usability planning and testing.
My opinion is that you should take some time to create a FD, but not to use it too much of the time. Consult every person that knows the process you are following with your system to find all the functions needed.
I have a lot of experience in software design, development, and selling, and I use functional decomposition as the first step of development. I use it as a base for the contract, so the client knows what they will get and I know what I must provide.
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
Are there objective metrics for measuring code refactoring?
Would running findbugs, CRAP or checkstyle before and after a refactoring be a useful way of checking whether the code was actually improved rather than just changed?
I'm looking for metrics that can be can determined and tested for to help improve the code review process.
Number of failed unittests must be less or equal to zero :)
Depending on your specific goals, metrics like cyclomatic complexity can provide an indicator for success. In the end every metric can be subverted, since they cannot capture intelligence and/or common sense.
A healthy code review process might do wonders though.
Would running findbugs, CRAP or checkstyle before and after a refactoring be a useful way of checking if the code was actually improved rather than just changed?
Actually, as I have detailed in the question "What is the fascination with code metrics?", the trend of any metrics (findbugs, CRAP, whatever) is the true added value of metrics.
It (the evolution of metrics) allows you to prioritize the main fixing action you really need to make to your code (as opposed to blindly try to respect every metric out there)
A tool like Sonar can, in this domain (monitoring of metrics) can be very useful.
Sal adds in the comments:
The real issue is on checking what code changes add value rather than just adding change
For that, test coverage is very important, because only tests (unit tests, but also larger "functional tests") will give you a valid answer.
But refactoring should not be done without a clear objective anyway. To do it only because it would be "more elegant" or even "easier to maintain" may be not in itself a good reason enough to change the code.
There should be other measures like some bugs which will be fixed in the process, or some new functions which will be implemented much faster as a result of the "refactored" code.
In short, the added value of a refactoring is not solely measured with metrics, but should also be evaluated against objectives and/or milestones.
Code size. Anything that reduces it without breaking functionality is an improvement in my book (removing comments and shortening identifiers would not count, of course)
No matter what you do just make sure this metric thing is not used for evaluating programmer performance, deciding promotion or anything like that.
I would stay away from metrics for measuring refactoring success (aside from #unit test failures == 0). Instead, I'd go with code reviews.
It doesn't take much work to find obvious targets for refactoring: "Haven't I seen that exact same code before?" For the rest, you should create certain guidelines around what not to do, and make sure your developers know about them. Then they'll be able to find places where the other developer didn't follow the standards.
For higher-level refactorings, the more senior developers and architects will need to look at code in terms of where they see the code base moving. For instance, it may be perfectly reasonable for the code to have a static structure today; but if they know or suspect that a more dynamic structure will be required, they may suggest using a factory method instead of using new, or extracting an interface from a class because they know there will be another implementation in the next release.
None of these things would benefit from metrics.
Yes, several measures of code quality can tell you if a refactoring improves the quality of your code.
Duplication. In general, less duplication is better. However, duplication finders that I've used sometimes identify duplicated blocks that are merely structurally similar but have nothing to do with one another semantically and so should not be deduplicated. Be prepared to suppress or ignore those false positives.
Code coverage. This is by far my favorite metric in general, but it's only indirectly related to refactoring. You can and should raise low coverage by writing more tests, but that's not refactoring. However, you should monitor code coverage while refactoring (as with any other change to the code) to be sure it doesn't go down. Refactoring can improve code coverage by removing untested copies of duplicated code.
Size metrics such as lines of code, total and per class, method, function, etc. A Jeff Atwood post lists a few more. If a refactoring reduces lines of code while maintaining clarity, quality has increased. Unusually long classes, methods, etc. are likely to be good targets for refactoring. Be prepared to use judgement in deciding when a class, method, etc. really does need to be longer than usual to get its job done.
Complexity metrics such as cyclomatic complexity. Refactoring should try to decrease complexity and not increase it without a well thought out reason. Methods/functions with high complexity are good refactoring targets.
Robert C. Martin's package-design metrics: Abstractness, Instability and Distance from the abstractness-instability main sequence. He described them in his article on Stability in C++ Report and his book Agile Software Development, Principles, Patterns, and Practices. JDepend is one tool that measures them. Refactoring that improves package design should minimize D.
I have used and continue to use all of these to monitor the quality of my software projects.
There are two outcomes you want from refactoring. You want to team to maintain sustainable pace and you want zero defects in production.
Refactoring takes place on the code and the unit test build during Test Driven Development (TDD). Refactoring can be small and completed on a piece of code necessary to finish a story card. Or, refactoring can be large and required a technical story card to address technical debt. The story card can be placed on the product backlog and prioritized with the business partner.
Furthermore, as you write unit tests as you do TDD, you will continue to refactor the test as the code is developed.
Remember, in agile, the management practices as defined in SCRUM will provide you collaboration and ensure you understand the needs of the business partner and the code you have developed meets the business need. However, without proper engineering practices (as defined by Extreme Programming) you project will loss sustainable pace. Many agile project that did not employ engineering practices were in need of rescue. On the other hand, a team that was disciplined and employed both management and engineering agile practice were able to sustain delivery indefinitely.
So, if your code is released with many defects or your team looses velocity, refactoring, and other engineering practices (TDD, paring, automated testing, simple evolutionary design etc), are not being properly employed.
I see the question from the smell point of view. Smells could be treated as indicators of quality problems and hence, the volume of identified smell instances could reveal the software code quality.
Smells can be classified based on their granularity and their potential impact. For instance, there could be implementation smells, design smells, and architectural smells. You need to identify smells at all granularity levels before and after to show the gain from a refactoring exercise. In fact, refactoring could be guided by identified smells.
Examples:
Implementation smells: Long method, Complex conditional, Missing default case, Complex method, Long statement, and Magic numbers.
Design smells: Multifaceted abstraction, Missing abstraction, Deficient encapsulation, Unexploited encapsulation, Hub-like modularization, Cyclically-dependent modularization, Wide hierarchy, and Broken hierarchy. More information about design smells can be found in this book.
Architecture smells: Missing layer, Cyclical dependency in packages, Violated layer, Ambiguous Interfaces, and Scattered Parasitic Functionality. Find more information about architecture smells here.