Side effects

本文主要学习了计算机语言中副作用的形成原因及应对。副作用是由于方法调用而改变对象的状态。可能是由于程序员意外导致的。例如:调用一个吸收器函数会改变某个对象字段的值。函数调用会更改调用所传递的实际参数的值等等。通过讨论这些问题来学习米特法则来应对可能在程序中产生的不利因素。
展开查看详情

1.CMPE 135: Object-Oriented Analysis and Design September 18 Class Meeting Department of Computer Engineering San Jose State University Fall 2018 Instructor: Ron Mak www.cs.sjsu.edu/~mak 1

2.Last Thursday Day class Accessors and mutators Dangerous setters Immutable classes Dangerous references to mutable objects const fields Separate accessors and mutators 2

3.3 Side Effects A side effect is a change to an object’s state due to a method call. Nasty side effects are unexpected by the programmer.

4.4 Nasty Side Effect Examples Calling a getter function changes the value of some object field. The dangerous setter functions of our original Day class. A function call changes the value of an actual parameter that’s passed by the call. A function call changes a global value, such as a static object.

5.5 Example Side Effect Basic date string parser: Advanced: Parse multiple dates in the string. Side effect: Function parse() updates parameter index to the string index of the next date. A better design: Add an index field to the date parser state. DateParser * date_parser = new DateParser (); string date_string = "September 18, 2018"; Date *d = date_parser ->parse( dateString ); int index = 0; Date *d = date_parser ->parse( dateString , index);

6.5 Example Side Effect Basic date string parser: Advanced: Parse multiple dates in the string. Side effect: Function parse() updates parameter index to the string index of the next date. A better design: Add an index field to the date parser state. DateParser * date_parser = new DateParser (); string date_string = "September 18, 2018"; Date *d = date_parser ->parse( dateString ); int index = 0; Date *d = date_parser ->parse( dateString , index);

7.7 The Law of Demeter , cont’d A member function should only use: Member variables of its class Parameters Objects that it constructs with new To obey this law: A method should never return a reference to an object that is part of its internal representation. Return a copy instead. A class should have sole responsibility to interact with objects that are part of its internal representation.

8.8 The Law of Demeter , cont’d The law enables you to modify the internal structure of a class without modifying its public interface . Encapsulation! Loose coupling!

9.9 How Good is an Interface? Who is the user of a class that you write? Other programmers? Perhaps you yourself, later!

10.10 How Good is an Interface? cont’d Class designer priorities Efficient algorithm Convenient coding etc. Class user priorities Easy to use Don’t have to understand the implementation etc.

11.11 How Good is an Interface? cont’d Is there a “conflict of interest” if you’re both the class designer and the class user? Can you make the right engineering tradeoffs ?

12.12 Cohesion A cohesive class implements a single abstraction or responsibility . Member functions should be related to this single abstraction.

13.13 Cohesion Why is this a badly designed class? Method processCommand () doesn’t belong. Delegate command processing to another class. class Mailbox { public: void add_message (Message * msg ); Message * get_current_message (); Message * remove_current_message (); void process_command (string command); ... }

14.14 Completeness Support operations for a well-defined abstraction . A potentially bad example: The Date class: How many milliseconds have elapsed? No such operation in the Date class. Does it fall outside the responsibility? We have before() , after() , get_time () Date start = new Date(); // do some work Date end = new Date();

15.15 Completeness If you encounter an incomplete class: Negotiate with the class designer. Fill in what’s missing with a subclass.

16.16 Convenience A good interface makes all tasks possible and common tasks simple . Example of inconvenience: The C++ string class Why are stoi , stof , to_string , etc. individual functions and not member functions of class string? To shift all the letters of a string to upper or lower case, you need to call the complicated transform function. How to do a case-insensitive string comparison?

17.17 Clarity Confused programmers write buggy code. Example: The C++ list class has many ways to insert elements into a list. Confusing. http://www.cplusplus.com/reference/list/list/insert/

18.Clarity , cont’d Another example: The C++ vector class A vector can be indexed using an integer value enclosed in [ and ] or by calling member function at with an integer value. But other member functions such as insert() and erase() require iterators to indicate positions within the vector. 18

19.19 Consistency Related features of a class should have matching names parameters return values behavior A bad example: Why is month 0-based? new GregorianCalendar(year, month - 1, day)

20.20 Programming by Contract Metaphor promoted by Bertrand Meyer. A pioneer of object-oriented programming. Inventor of the Eiffel programming language. Ensure that: All class constructors only create objects with valid initial states . All mutators preserve the valid states.

21.21 Programming by Contract , cont’d Therefore: We should never have invalid objects. No need to waste run time checking for invalid objects.

22.22 Programming by Contract , cont’d Member functions are agents that fulfill a contract . The contract spells out the responsibilities Of the caller (the class user) Of the implementer (you, the programmer) Involves preconditions , postconditions , and invariants .

23.23 Preconditions The MessageQueue class What should happen if the class user attempts to remove a message from an empty queue? class MessageQueue { public: void add(Message * msg ); Message *remove(); Message *peek(); int size(); ... private: vector<Message *> elements; }

24.Preconditions , cont’d What should happen if the class user attempts to remove a message from an empty queue? Class MessageQueue can declare this to be a runtime error. Class MessageQueue can simply return null . Which is better? class MessageQueue { public: void add(Message * msg ); Message *remove(); Message *peek(); int size(); ... private: vector<Message *> elements; }

25.Preconditions , cont’d Excessive error checking is costly. Returning dummy values can complicate testing. 25

26.26 Preconditions , cont’d A precondition is a condition that must be true before the service provider can promise to fulfill its part of the contract. If the precondition is not true and the service is still requested, the provider can choose any action that is convenient for it, no matter how disastrous the outcome may be for the service requester.

27.27 Contract Metaphor The service provider (i.e., the class) must specify preconditions. If the precondition is true , the service provider must work correctly . If the precondition is not true, the service provider can do anything . Throw an exception? Return a default or false answer? Corrupt data? Handle the error gracefully?

28.28 Preconditions , cont’d What happens if the precondition not fulfilled? remove() makes no promises to do anything sensible if called on an empty queue. /** * Remove the message at the head. * @return the message at the head * @precondition size() > 0 */ Message * MessageQueue :: remove() { Message *r = elements[0]; elements->erase(0); return r; }

29.29 Example: Queue as a Circular Array The current MessageQueue implementation removes messages inefficiently. After the head message (element 0) is removed, all the remaining messages must shift one position: