Software Engineering Lecture 2 Annatala Wolf

carenextSoftware and s/w Development

Nov 18, 2013 (3 years and 9 months ago)

97 views

Software Engineering Lecture 2

Annatala

Wolf


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



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

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!


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.)


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)


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.


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.


When you’re
thinking like an implementer
of a
system, you’re thinking in
concrete

terms.



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.


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.


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
.


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.


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.




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.


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.)


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.


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
is
one of the most powerful tools we have
.


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).


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
!

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
.


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 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.


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.


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?


This approach, where the user of a system relies
on specs rather than how code is written is called
Design
B
y Contract
. It’s what makes the
creation of large software systems possible.


DBC is also known as “programming
t
o 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.


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.


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.


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
.


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
.


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).


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.)


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.


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).


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.


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.


The client code
checks

preconditions of the
methods it uses; the implementer code
assumes

its preconditions are true!


Similarly, client code
assumes

that the post
-
condition (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…