Software Engineering Lecture 2 Annatala Wolf COMPONENTS AND

  • Slides: 36
Download presentation
Software Engineering Lecture 2 Annatala Wolf COMPONENTS AND CONTRACTS

Software Engineering Lecture 2 Annatala Wolf COMPONENTS AND CONTRACTS

System* � A system is a way of thinking about a set of interconnected

System* � A system is a way of thinking about a set of interconnected things that work together. � Systems are composed of subsystems: smaller pieces which are also systems by themselves. � Examples of systems you know: �The Solar System �your body’s nervous system �your computer’s operating system

Abstraction* � The power of using the idea of a “system” is that it

Abstraction* � The power of using the idea of a “system” is that it allows us to take something very complicated and break it into pieces that work independently. �If we couldn’t do this with software, we couldn’t write large programs! � This simplification of how we view things in a system is called abstraction. It’s a goal-oriented, high-level view of things, rather than becoming immersed in the details.

Abstract vs. Concrete � Abstract things are broad descriptions or concepts useful for understanding

Abstract vs. Concrete � Abstract things are broad descriptions or concepts useful for understanding what something does. An abstract description of what a car does might be, “it’s a small motorized device used to transport people and goods”. � Concrete things are specific details in which the overall purpose of the system can get lost. A concrete description of a car would be complex!

Solutions are Abstract* � When we use computers, what we want is something abstract,

Solutions are Abstract* � When we use computers, what we want is something abstract, not concrete: �Communicate a message �Answer a math problem �Make horrific pornography appear on the screen � We don’t often care about the details of how this gets done. (Nobody runs a program in order to set the zeroes and ones to a particular value. )

Thinking Roles � However, we sometimes have no choice but to care about concrete

Thinking Roles � However, we sometimes have no choice but to care about concrete details. �Someone has to build a car before you can drive it! � There are two roles that we think in, depending on whether we are handling the abstract details or the concrete details: �Client: the user of something (abstract view) �Implementer: the designer or fixer (concrete view)

Roles Are Not People � In software, you play both roles, and usually at

Roles Are Not People � In software, you play both roles, and usually at the same time! � You are a client of a class any time you use an object of that class type. You are a client of an operation any time you call that operation. � You are an implementer of a class or operation when you write or edit the code that makes it function properly.

Being a Client � When you’re thinking like a client of a system, you’re

Being a Client � When you’re thinking like a client of a system, you’re thinking in abstract terms. � For example: when I go to pour a glass of water from the sink, I don’t immerse myself in thinking about how the faucet works. Instead, I just use the interface the faucet gives me (turning knobs) to get the result I want. I’m focused on the goal, and only what I must do to get there.

Being an Implementer � When you’re thinking like an implementer system, you’re thinking in

Being an Implementer � When you’re thinking like an implementer system, you’re thinking in concrete terms. of a � If I’m repairing a faucet, I need to understand how it works. The details now matter, because my goal has changed. I’m not trying to use the system, I’m trying to fix it…and to fix it, I need to understand how its subsystems make it work.

One Level at a Time � Implementers don’t need to focus on how the

One Level at a Time � Implementers don’t need to focus on how the subsystems work! A plumber needn’t know how plumbing pipes are built, as long as they know how to use them (as a client!) to make repairs. � In other words, an implementer looks at the whole system concretely, in terms of its pieces. But when you do this, you think about the subsystems abstractly, as a client! Otherwise, you’d be overwhelmed by the information.

Distinction in Code � Any time you call an operation or use a component,

Distinction in Code � Any time you call an operation or use a component, that’s client code. � All code is implementer code, however! The code inside the body of an operation or component is its implementer code. � So it’s not like some section of code is client or implementer. Different places in code are client or implementer of different operations and components.

Components � In order to write large software, we need to design things so

Components � In order to write large software, we need to design things so that code in one place won’t break code in another place when it changes. � The way to do this is to divide code up into component pieces. In object-oriented programming, these components are chiefly the classes (i. e. , definitions for new data types). � We need to separate these components with some abstract boundary, for safety.

Information Hiding � To facilitate component separation, we use something called information hiding. Our

Information Hiding � To facilitate component separation, we use something called information hiding. Our goal is to take the data that represents something and hide it from the client side. � Instead of accessing the data directly, the client will rely on methods that we provide them.

Abstraction � In order to hide information, implementer code must replace the hidden information

Abstraction � In order to hide information, implementer code must replace the hidden information with something else the client can understand. � Abstraction is the process of simplifying data access by providing a conceptual description or “cover story” which leaves out implementation details. The description tells a client what happens, not how it happens.

Encapsulation � So, when we design a class, we really want to think about

Encapsulation � So, when we design a class, we really want to think about encapsulating (surrounding by an abstract barrier) the data, and include code that works on the data. This code will allow the client to manipulate the data indirectly, from an abstract (i. e. , client-based) perspective. � The specifications for code we use to access hidden data are called an interface. (The word interface has a similar special meaning in Java. )

The Car Analogy � Cars have a well-defined interface: a gas pedal, brake pedal,

The Car Analogy � Cars have a well-defined interface: a gas pedal, brake pedal, steering wheel, gear shift, lights, and the turn signal that most assholes don’t seem to understand exists. � But this interface is highly abstract! Cars don’t give you access to any of the details. There’s no knob that lets you change the rate at which fuel is injected, or how fast the pistons fire.

Limitations are Our Friends � But this is a good thing! If we did

Limitations are Our Friends � But this is a good thing! If we did have access to such details, think about what might happen: �I would most certainly destroy my goddamn car. �I’d have to take classes in engineering, just to drive! �The way my car drives may not translate to other cars. �If my car gets repaired, I might need to relearn driving. � In software, restricting what we have access to one of the most powerful tools we have. is

What Abstraction Gives Cars � Strange as it seems, ignorance is empowering. Since I

What Abstraction Gives Cars � Strange as it seems, ignorance is empowering. Since I don’t need to know these details, that abstract car interface gives me four benefits: �Safety: I won’t destroy my car (as easily). �Simplicity: I don’t need to learn as much to drive. �Interchangeability: I can drive anyone else’s car!* �Maintainability: If my car changes, I can still drive it. * Provided it has the same interface (e. g. , not a stick shift).

What Interfaces Give Code � In software development, we see the same benefits when

What Interfaces Give Code � In software development, we see the same benefits when we encapsulate and protect data by relying only on interfaces (specifications). �Safety: You’re less likely to misuse a component. �Simplicity: It’s easier to learn to use the component. �Interchangeability: You can change or choose which component version you’re using, even at run-time! �Maintainability: Changing how the component works won’t break other components. This is critical!

The Big Picture* Protected Data (engine, wires, gears) The class is responsible for storing

The Big Picture* Protected Data (engine, wires, gears) The class is responsible for storing and interpreting its objects’ data. Users don’t need to know how it works! Interface (steering, gas, brake) The client (user) of an object uses the interface (methods) of the object to access and change its value. The data is private. The user doesn’t need to know how the object works. They should think about the object abstractly, in terms of what its expected behavior should be over time.

Terminology Note � There’s a difference between interface meaning “a public interface to an

Terminology Note � There’s a difference between interface meaning “a public interface to an encapsulated object”, and a Java interface, which is a very particular kind of abstract Java component. � However, Java interfaces are closely related to the interface concept: they’re the best place to describe our public interface to an object type.

Java Interfaces � Java classes come in three flavors of abstraction: �Concrete classes (sometimes

Java Interfaces � Java classes come in three flavors of abstraction: �Concrete classes (sometimes just called classes) are the only class types you can make objects of, by using the new operator. These are totally concrete. �Java’s interfaces are the most abstract component type in Java. They can’t have any concrete code (apart from constants), so they basically hold… interfaces! �Abstract classes are in-between. You can’t make objects from them, but they may still contain code.

Depend on Abstraction* � When designing code, you want components to depend on the

Depend on Abstraction* � When designing code, you want components to depend on the most abstract descriptions possible. This makes your components easier to use and flexible to change. � We’ll look at this in more detail later on.

Contracts � There’s an important idea we’ve mentioned a few times now: reliance on

Contracts � There’s an important idea we’ve mentioned a few times now: reliance on specifications (abstract view) rather than on concrete code. � In order for this to work, we need an agreement between the client and implementer: a contract that tells us what we need to use the component. �What does this operation (or class) do? �Under what circumstances can we assume it will work?

Design By Contract � This approach, where the user of a system relies on

Design By Contract � This approach, where the user of a system relies on specs rather than how code is written is called Design By Contract. It’s what makes the creation of large software systems possible. �DBC is also known as “programming to the interface”. � A contract (i. e. specifications) need not cover every situation! Requirements can be placed on the client. For example, the operation of your car is not guaranteed if you turn it off while racing.

Preconditions � A precondition is a description of the necessary circumstances under which an

Preconditions � A precondition is a description of the necessary circumstances under which an operation will actually work correctly. � The client (the code that calls the operation) is only guaranteed the operation will do anything if the precondition is met! � So the client is the one who must check preconditions before calling an operation.

Postconditions � A postcondition is a description of what the operation does. � The

Postconditions � A postcondition is a description of what the operation does. � The implementer is responsible for making sure the code actually fulfills this specification. � However, an implementer should freely assume that the precondition has been met, since the code is only guaranteed to work when it’s true. � Not assuming this can lead to over-checking, and this can hide errors from you during testing.

Isolating Checks � By not double-checking, code may be much easier to write. You

Isolating Checks � By not double-checking, code may be much easier to write. You can leave assertions in to check your preconditions when coding, and after testing, remove them for better performance and improved error-detection. � When you’re testing, however, you want errors in preconditions to explode the program as openly as possible! A failed precondition means your code isn’t written properly.

Assert Statements � Note that assert statements do not contradict design by contract principles!

Assert Statements � Note that assert statements do not contradict design by contract principles! � These statements are here to ensure when you fail an assert during testing, you immediately are aware of where the problem is and what you did wrong (essentially, your code violated a precondition). � These don’t need to be compiled in release mode (after testing), but only in debug/test mode.

Limitations of Checking � You might wonder: is it always a good idea to

Limitations of Checking � You might wonder: is it always a good idea to place the onus of checking on the user of a component? What if you miss something during testing—will that become unrecoverable error? � Code can be written to be fault-tolerant, yes. But you really want to catch misuse of a component; the contract may not even make sense if you violate it. Just realize that for the end user, a precondition should be simple (if not always true).

Formal Contracts � The contracts we’ll use in this class are rather formal in

Formal Contracts � The contracts we’ll use in this class are rather formal in nature, which isn’t something you’ll see in industry (at least, not anytime soon). � The good news is this: if you can actually read mathematical language, formal contracts are completely unambiguous! (Since every computer scientist should be able to read math, we don’t think this is an unrealistic expectation. )

Mathematical Modeling � In keeping with this mathematical formality, every kind of object will

Mathematical Modeling � In keeping with this mathematical formality, every kind of object will have an underlying mathematical model that describes its behavior. � We’ll cover this in depth later, but really all you need to understand are Boolean values, integers, reals, Unicode characters, sets, strings (the mathematical kind), tuples, and trees. We’ve already covered the former four and we’ll discuss the latter four later on.

Using Javadocs � Our contracts will appear in Javadoc format. � Javadocs are special

Using Javadocs � Our contracts will appear in Javadoc format. � Javadocs are special comments that create documentation automatically. You can (and should!) place one before each class, method, or data member (class or instance variable). � Javadocs are entered just like multi-line comments, except they start with /** (two asterisks rather than just one).

Annotations � Our contracts will contain Java annotations which identify the parts of a

Annotations � Our contracts will contain Java annotations which identify the parts of a specification: �@requires precedes the precondition. �@ensures precedes the postcondition. �@param precedes parameter definitions. �@return precedes a description of the return value, though @ensures also describes what a function returns by using the name of the function itself.

Method Specifications � Javadoc specifications on methods will also contain a parameter mode for

Method Specifications � Javadoc specifications on methods will also contain a parameter mode for each parameter. This will be necessary when we start working with mutable reference types, because a big part of what any method does will involve what happens to the objects to which it was passed a reference. � We’ll discuss this later when the subject arises.

Remember! � The client code checks preconditions of the methods it uses; the implementer

Remember! � The client code checks preconditions of the methods it uses; the implementer code assumes its preconditions are true! � Similarly, client code assumes that the postcondition (the thing the method should do) actually happens after the call. The implementer code must make that happen. � This is a commonly missed test question…