340 likes | 407 Views
Introduction. Advanced OOP CS 440/540 Spring 2014 Kenneth Chiu. Requirements. What are they? What kinds are there? Functionality Performance Platform/dependencies How are they specified? API Documentation Input files, output files Performance metrics. Design. What is a good design?
E N D
Introduction Advanced OOPCS 440/540Spring 2014Kenneth Chiu
Requirements • What are they? What kinds are there? • Functionality • Performance • Platform/dependencies • How are they specified? • API • Documentation • Input files, output files • Performance metrics
Design • What is a good design? • What is a bad design? • What makes one design better or worse than another? • Faster • Easier to understand • Easier to modify • Is sometimes a slower executing design better than a faster executing design? • Can we objectively measure how good a design is?
Why Is Software Complex? • The problem itself is complex. • Difficulty of managing the process. • Two different teams may not be completely in sync on the way that two pieces of code interact, leading to complexity. • The flexibility of software. • Creeping featurism. • Changing requirements. • The world itself changes. • Which changes the requirements. But the world does not change all at once in a predictable way, so usually changes are handled incrementally. • What’s wrong with incremental changes? • Why not design it right in the first place? • “Accidental” vs. essential • Accidental: Interface not quite right might require an adaptor. • Essential: Problem domain is complex.
Managing Complexity • This course is about managing complexity. • Once you get beyond the basics, that’s what programming is about. • Have you ever said: “I didn’t have time to make it simpler.” • It’s hard to make things simple. • Can we eliminate complexity? • What strategies can we use to manage complexity? • Complexity should be localized as much as possible. • This helps lead to the things we think of as a good design. • How is an airplane structured? • Decomposition, but how? • Algorithmic • OO
Code Reuse • Why is code reuse good? • How does it help us manage complexity? • What’s wrong with copy-and-paste? • It concentrates (localizes) complexity. • How do we get code reuse? • Parameterization is crucial.
How do we get code reuse? • i = j*a + j*b;…i = j*a + j*b; • i = f(j, a, b);…i = f(j, a, b); • #define xyzzy() i = j*a + j*bxyzzy();…xyzzy();#undefxyzzy
How do we get code reuse? • for (inti = 0; i < 10; i++) { // Code fragment using three // variables. // …}// …for (inti = 0; i < 100; i++) { // Same code fragment. // …} • loop(10, /* Pass variables */);…loop(100, /* Pass variables */);
Consider a case like this. How do we get code reuse? • bool less(inti, int j) { return i < j;}bool greater(inti, int j) { return i > j;}void doit(int op1, int op2,bool (*cmp)(int, int)) {// … (Fragment A) if ((*cmp)(op1, op2)) { // … (Fragment B) } // … (Fragment C)}// …doit(i, j, &less);doit(k, l, &greater); • // … (Fragment A)if (i < j) { // … (Fragment B)}// … (Fragment C)// … (Fragment A, repeated)if (k > l) { // … (Fragment B, repeated)}// … (Fragment C, repeated)
Another way: • fragA();if (i < j) {fragB();}fragC();// …fragA();if (k > l) {fragB();}fragC(); • // … (Fragment A)if (i < j) { // … (Fragment B)}// … (Fragment C)// … (Fragment A, repeated)if (k > l) { // … (Fragment B, repeated)}// … (Fragment C, repeated) • What if the fragments need access to local variables?
We can pass those as parameters. • fragA(&i, j, k, &l);if (i < j) {fragB(a, b, &c);}fragC(&x, &y);// …fragA(&i, j, k, &l);if (k > l) {fragB(a, b, &c);}fragC(&x, &y);
Or, in the case where we factored out the comparison function: • bool less(inti, int j) { return i < j; }bool greater(inti, int j) { return i > j; }void doit(int&i, int &j, int &k,double &x, double &y,int&a, int &b, int &c,int op1, int op2, bool(*cmp)(int, int)) { // … (Fragment A if ((*cmp)(op1, op2)) { // … (Fragment B } // … (Fragment C}// …doit(i, j, i, j, k, l, x, y, a, b, c, less);doit(k, l, i, j, k, l, x, y, a, b, c, greater);
Can wrap the parameters in an object. • inti, j;double x, y;int a, b, c;struct Helper { Helper(int &i_, int &j_, int &k_, int &l_, double &x_, double &y_,int &a_, int &b_, int &c_) : i(i_), j(j_), k(k_), x(x_), y(x_), a(a_), b(b_), c(b_) {}int &i, &j; double &x, &y;int &a, &b, &c; void fragA() { /* … */ } void fragB() { /* … */ } void fragC() { /* … */ }} h(i, j, k, l, x, y, a, b, c);
It’s usually a bit cleaner looking and concise to just put the variables inside the struct. • struct {inti, j, k; double x, y;inta, b, c; void fragA() { /* … */ } void fragB() { /* … */ } void fragC() { /* … */ }} h;h.i = 0; h.j = 0; h.k = 0;h.x = 3.14; h.y = 2.2;h.a = 2; h.b = 3; h.c = 5; • h.fragA();if (h.i< h.j) {h.fragB();}h.fragC();// …h.fragA();if (h.k> l) {h.fragB();}hlp.fragC();
So now, rather than this: • fragA(&i, j, k, &l);if (i < j) {fragB(a, b, &c);}fragC(&x, &y);// …fragA(&i, j, k, &l);if (k > l) {fragB(a, b, &c);}fragC(&x, &y); • We have this: • h.fragA();if (i < j) {h.fragB();}h.fragC();// …h.fragA();if (k > l) {h.fragB();}hlp.fragC();
Rather than passing local variables to helper functions, or storing them in helper objects, we can use a lambda (C++11) to automatically capture them. • std::function<bool()> ij_less = [&]() { return i < j; };std::function<bool()> kl_greater = [&]() { return k > l; };auto doit = [&](std::function<bool()> &cmp) { // ... (Fragment A) if (cmp()) { // ... (Fragment B) } // ... (Fragment C)};doit(ij_less);doit(kl_greater); • doit([&]() { return i < j; });doit([&]() { return k > l; });
We can also use lambdas if we want to turn the fragments into functions. • auto fragA= [&]() { /* … */ };auto fragB= [&]() { /* … */ };auto fragC= [&]() { /* … */ };fragA();if (i < j) { fragB(); }fragC();fragA();if (k > l) { frag B(); }fragC();
Probably the nicest way is to use lambda’s just for the comparisons: • for (auto cmp: std::vector<std::function<bool()>>{ [&]() { return i < j; }, [&]() { return k < l; }, }) { // … (Fragment A) if (cmp()) { // … (Fragment B) } // … (Fragment C)}
We can also just use a macro. • #define xyzzy_doit(op1, op2, cmp) \/* … (Fragment A) */ \if (op1 cmp op2) { \/* … (Fragment B) */ \} \/* … (Fragment C) */ • xyzzy_doit(i, j, <)xyzzy_doit(k, l, >) • Why the funny prefix? • This is actually pretty clean looking. What’s the downside?
Hierarchy • What is it? • A ranking of abstractions. Example? • Employee, manager. • Why? • Maximizes other reuse, improves abstractions. • If we just had Under and Grad, there would be no way to write code that didn’t care about the difference.
Example • A hierarchy for dishes. • By country? • By style? • By content? • Often no one best hierarchy, without considering use cases. • MI can help resolve conflicting use cases.
Two kinds of hierarchy • Consider: • An engine is part of a truck. • A diesel engine is a kind of engine. • First is considered part-of, second is considered is-a. • What do they correspond to? • Object hierarchies • Class hierarchies • So, can you have an object hierarchy without a class hierarchy? How about a class hierarchy without an object hierarchy? • These two hierarchies are orthogonal.
Nature of an Object • What is an object, in OOP? • What is or is not an object? • red? • airplanes? • processes? • Figuring these out is part of analysis phase. • What qualities should objects have? • Abstraction • Encapsulation • Modularity • Hierarchy
When to Make Things a Separate Class • A checking account: • Has a monthly fee. • Does not pay interest. • A savings account: • Has no monthly fee. • Pays monthly interest. • Should we make these separate classes?
To design, first figure out what the operations are on your objects and what state they should have. • Then figure out how to abstract/encapsulate. Is there a cost to encapsulation? • Could you do OOP before C++/Java? • Objects are the fundamental building blocks, not algorithms.
Summary of Classification/Hierarchies • Given any domain (such as dishes), there are many different ways to classify/organize the entities. • In code, we can only have a single class organization. • So we have to pick something. • MI can be used to alleviate. • Other aspects have to implicit, rather than explicit. • Or sometimes can be represented in the object hierarchy.
Abstraction • What is it? • Abstraction is usually performed over a set of similar classes. • Abstraction is simplifying things. • What’s the minimal set of public state and operations that your objects (conforming to the same interface) need to provide to the rest of your code? • Principle of least commitment. • Abstractions should be minimal.
Abstraction is more than just creating a list of operations. • Sometimes there are multiple ways to represent the same thing. • What’s the abstraction of a 2-D point? • class Point {get_x();get_y();set_x(x);set_y(y);}; • class Point {get_theta();get_r();set_theta(t);set_r(r);} • How do we solve this? • Represent internally in one of these. Provide all methods. But if we pick the wrong internal representation, then there will be a high conversion cost. • Consider these to be two different abstractions. Pick one of them. • Some things are “too abstract” to hope for a computer representation that is ideal.
Encapsulation • What is it? • Hiding the implementation or other details. • Is there a difference between abstraction and encapsulation? • Can you abstract without encapsulating? • Can you encapsulate without abstracting? • Flip sides of the same coin.
Waterfall Model • Requirements • Analysis • Design • Implementation
Iterative Development • Give up trying to get the design right from the beginning. • Instead, accept that it’s going to take multiple iterations, and try to make streamline that process.
Defensive Programming • Most programs are riddled with bugs. • Most bugs (like cockroaches) are hidden, unrevealed. • When long-hidden bugs show themselves, they are hard to exterminate. Why? • Place where bug shows itself is far removed from where bug actually occurs. • The bug was actually put into the code a long time ago. • Moral: Exterminating bugs is easiest when you catch them in their nest, and when you catch them early. • The purpose of defensive programming is to force bugs to show themselves as early as possible, and as close to the origin of the bug as possible. • Think about what programmers using your code might do, long after you wrote the code. • They may interpret things slightly differently, and use or modify your code in the wrong way. • Try to force such bugs to reveal themselves early and quickly.
Robustness • What does “robust” mean? • You can have source robustness. • Portable • Likely to catch errors that other programmers do. • Is this robust? • class String { public: String(const char *s) : str(strdup(s)) { } ~String() { free(str); } private: const char *const str;}; • You can have run-time robustness. • Likely to respond well to abnormal conditions. • Example conditions?
Let’s say that you are writing a 100 line program. • One of the functions has a potential problem, but you estimate that the chance of it happening is only 1 in 1,000,000 every time the program is run. • You figure it’s okay to ignore. • Good idea? • If there are 10,000 such functions, the probably of failure becomes 1 out of 100.