Thinking Asynchronously: Designing Applications with Boost.Asio Chris Kohlhoff

The Basics

The Basics

asio::io_service io_service; // ... tcp::socket socket(io_service); // ... socket.async_connect( server_endpoint, your_completion_handler); // ... io_service.run();

The Basics

asio::io_service io_service; // ... tcp::socket socket(io_service); // ... socket.async_connect( server_endpoint, your_completion_handler); // ... io_service.run();

The Basics

asio::io_service io_service; // ... tcp::socket socket(io_service); // ... socket.async_connect( server_endpoint, your_completion_handler); // ... io_service.run();

I/O Object

The Basics

asio::io_service io_service; // ... tcp::socket socket(io_service); // ... socket.async_connect( server_endpoint, your_completion_handler); // ... io_service.run();

Asynchronous operation

The Basics

asio::io_service io_service; // ... tcp::socket socket(io_service); // ... socket.async_connect( server_endpoint, your_completion_handler); // ... io_service.run();

The Basics

socket.async_connect( server_endpoint, your_completion_handler);

The Basics

socket.async_connect( server_endpoint, your_completion_handler);

I/O Object

The Basics

socket.async_connect( server_endpoint, your_completion_handler);

Initiating function

The Basics

socket.async_connect( server_endpoint, your_completion_handler);

Arguments

The Basics

socket.async_connect( server_endpoint, your_completion_handler);

Completion handler

The Basics

socket.async_connect( server_endpoint, your_completion_handler);

Completion handler

void your_completion_handler( const boost::system::error_code& ec);

The Basics

socket.async_connect( server_endpoint, your_completion_handler);

io_service

Operating System

The Basics

socket.async_connect( server_endpoint, your_completion_handler);

io_service

creates

work handler Operating System

The Basics

io_service.run() io_service

work handler Operating System

The Basics

io_service.run() io_service

notifies

work handler

Operating System

The Basics

io_service.run() io_service

dequeues

work handler Operating System

The Basics

result handler

io_service.run() io_service

dequeues

work handler Operating System

The Basics

your_completion_handler(ec); result handler

io_service.run() io_service

Operating System

The Basics

socket socket timer

timer

serial port

io_service

work

work work handler

handler

handler work handler

work handler

The Basics

io_service.run() io_service

work

work work handler

handler

handler work handler

work handler

The Basics

io_service.run() dequeues

io_service

work

work work handler

handler

handler work handler

work handler

The Basics

result handler

io_service.run() dequeues

io_service

work

work work handler

handler

handler work handler

work handler

The Basics

result handler

io_service.run() io_service

work work handler

handler work handler

work handler

The Basics

io_service.run() io_service

work work handler

handler work handler

work handler

The Basics

io_service.run() io_service

dequeues work work handler

handler work handler

work handler

The Basics

result handler

io_service.run() io_service

dequeues work work handler

handler work handler

work handler

The Basics

result handler

io_service.run() io_service

work handler

work handler

work handler

The Basics

socket.async_read_some(...); result handler

io_service.run() io_service

work handler

work handler

work handler

The Basics

socket.async_read_some(...); result handler

io_service.run() io_service

work handler

work handler

work handler

The Basics

socket.async_read_some(...); result handler

io_service.run() io_service

creates

handler

work handler

work

work handler

work handler

The Basics

io_service.run() io_service

work handler

work handler

work handler

work handler

The Basics

socket.async_connect( server_endpoint, your_completion_handler);

Asynchronous operation

Completion handler

The Basics

socket.async_connect(...);

if (!ec) { socket.async_read_some(...); } if (!ec) { async_write(socket, ...); }

Challenges: ●

Object lifetimes



Thinking asynchronously



Threads



Managing complexity

Object Lifetimes

Object Lifetimes

socket.async_connect(server_endpoint, your_completion_handler_1); socket.async_read_some(buffers, your_completion_handler_2); async_write(socket, buffers, your_completion_handler_3); acceptor.async_accept(socket, peer_endpoint, your_completion_handler_4);

Object Lifetimes

socket.async_connect(server_endpoint, your_completion_handler_1); socket.async_read_some(buffers, your_completion_handler_2); async_write(socket, buffers, your_completion_handler_3); acceptor.async_accept(socket, peer_endpoint, your_completion_handler_4);

By value

Object Lifetimes

socket.async_connect(server_endpoint, your_completion_handler_1); socket.async_read_some(buffers, your_completion_handler_2); async_write(socket, buffers, your_completion_handler_3); acceptor.async_accept(socket, peer_endpoint, your_completion_handler_4);

By const reference

Object Lifetimes

socket.async_connect(server_endpoint, your_completion_handler_1); socket.async_read_some(buffers, your_completion_handler_2); async_write(socket, buffers, your_completion_handler_3); acceptor.async_accept(socket, peer_endpoint, your_completion_handler_4);

By non-const reference

Object Lifetimes

socket.async_connect(server_endpoint, your_completion_handler_1); socket.async_read_some(buffers, your_completion_handler_2); async_write(socket, buffers, your_completion_handler_3); acceptor.async_accept(socket, peer_endpoint, your_completion_handler_4);

this pointer

Object Lifetimes

What's wrong with this code? void do_write() { std::string message = ...; async_write(socket, asio::buffer(message), your_completion_handler); }

Object Lifetimes

socket.async_connect(...);

if (!ec) { socket.async_read_some(...); } if (!ec) { async_write(socket, ...); }

Object Lifetimes

int main { asio::io_service io_service; connection conn(io_service); io_service.run(); }

Object Lifetimes

Question: ●

When does object lifetime begin and end?

Object Lifetimes

What's wrong with this code? class connection { tcp::socket socket_; vector data_; // ... ~connection() { socket_.close(); } // ... };

Object Lifetimes

socket.async_connect(...);

if (!ec) { socket.async_read_some(...); } if (!ec) { async_write(socket, ...); }

Object Lifetimes

socket.async_read_some(...);

io_service

creates

handler

work handler

work

work handler

work handler

Object Lifetimes

socket.async_connect(server_endpoint, your_completion_handler_1); socket.async_read_some(buffers, your_completion_handler_2); async_write(socket, buffers, your_completion_handler_3); acceptor.async_accept(socket, peer_endpoint, your_completion_handler_4);

By value

Object Lifetimes

class connection : enable_shared_from_this { tcp::socket socket_; vector data_; // ... };

Object Lifetimes

class connection : enable_shared_from_this { tcp::socket socket_; vector data_; // ... void start_write() { async_write(socket_, asio::buffer(data_), bind(&connection::handle_write, shared_from_this(), _1, _2)); } // ... };

Object Lifetimes

class connection : enable_shared_from_this { tcp::socket socket_; vector data_; // ... void stop() { socket_.close(); } // ... };

Object Lifetimes

class connection : enable_shared_from_this { tcp::socket socket_; vector data_; // ... void start() { socket_.async_connect(...); } // ... };

Object Lifetimes

class connection : enable_shared_from_this { tcp::socket socket_; vector data_; // ... void start() { socket_.async_connect(...); } // ... }; // ... make_shared(...)->start();

Thinking Asynchronously

Thinking Asynchronously

class connection { tcp::socket socket_; vector data_; // ... ~connection() { socket_.close(); } // ... };

Thinking Asynchronously

What's wrong with this code? double Now() { timeval tv; gettimeofday(&tv, 0); return time(0) + tv.tv_usec / 1000000.0; }

Thinking Asynchronously

gettimeofday()

Operating System

time()

Thinking Asynchronously

What's wrong with this code? if (socket.IsConnected()) { socket.Write(data); }

Thinking Asynchronously

IsConnected()

Write()

TCP State Machine Operating System

Thinking Asynchronously

class connection : enable_shared_from_this { tcp::socket socket_; vector data_; // ... void stop() { socket_.close(); } // ... };

Thinking Asynchronously

class connection : enable_shared_from_this { tcp::socket socket_; vector data_; // ... void handle_write(error_code ec) { if (!socket_.is_open()) return; // ... } // ... };

Thinking Asynchronously

class connection : enable_shared_from_this { tcp::socket socket_; vector data_; // ... bool is_stopped() const { return !socket_.is_open(); } // ... };

Thinking Asynchronously

class connection : enable_shared_from_this { // ... void start(); void stop(); bool is_stopped() const; // ... };

Threads

Threads

Spectrum of approaches: ●

Single-threaded



Use threads for long-running tasks



Multiple io_services, one thread each



One io_service, many threads

Threads

Single-threaded: ●

Preferred starting point



Keep handlers short and non-blocking

Threads

Use threads for long running tasks: ●

Logic stays in main thread



Pass work to background thread



Pass result back to main thread

Threads

io_service.run() io_service

work handler

work handler

work handler

Threads Thread work

creates

launch task in thread

handler

work handler

work handler

work handler

Threads Thread work handler

io_service.run() io_service

work handler

work handler

work handler

Threads Thread work handler

io_service.run() io_service

posts work result handler

work handler

work handler

work handler

Threads

result handler

io_service.run()

dequeues

io_service

work result handler

work handler

work handler

work handler

Threads

result handler

io_service.run() io_service

work handler

work handler

work handler

Threads

asio::io_service::work

work handler

Threads

class connection : enable_shared_from_this { // ... asio::io_service& io_service_; // ... void start_work() { async( bind(&connection::do_work, shared_from_this(), ...args..., asio::io_service::work(io_service_))); } // ... };

Threads

class connection : enable_shared_from_this { // ... asio::io_service& io_service_; // ... void do_work(...args..., asio::io_service::work) { // long running task ... io_service_.post( bind(&connection::work_done, shared_from_this(), ...result...)); } // ... };

Threads

class connection : enable_shared_from_this { // ... void work_done(...result...) { // ... } // ... };

Threads

asio::io_service async_io_service; asio::io_service::work async_work(async_io_service); boost::thread async_thread( bind(&asio::io_service::run, &async_io_service)); // ... template void async(Handler h) { async_io_service.post(h); }

Threads

Multiple io_services, one thread each: ●

Logic stays in each object's “home” thread



Keep handlers short and non-blocking



Objects communicate via “message passing”

Threads

class connection : enable_shared_from_this { // ... asio::io_service& io_service_; // ... void do_foobar(...args...) { ... } public: void foobar(...args...) { io_service_.post( bind(&connection::do_foobar, shared_from_this(), ...args...)); } // ... };

Threads

class connection : enable_shared_from_this { // ... asio::io_service& io_service_; // ... void do_foobar(...args...) { ... } public: void foobar(...args...) { io_service_.dispatch( bind(&connection::do_foobar, shared_from_this(), ...args...)); } // ... };

Threads

One io_service, multiple threads: ●

Handlers can be called on any thread



Perform logic in strands



Objects communicate via “message passing”

Threads

Strands: ●

Non-concurrent invocation of handlers



May be implicit or explicit

Threads

connection

connection

socket.async_connect(... );

socket.async_connect(... );

if (!ec) { socket.async_read_some(...); }

if (!ec) { socket.async_read_some(...); }

if (!ec) { async_write(socket, ...); }

connection

if (!ec) { async_write(socket, ...); }

connection

socket.async_connect(... ); socket.async_connect(... );

if (!ec) { socket.async_read_some(...); } if (!ec) { async_write(socket, ...); }

if (!ec) { socket.async_read_some(...); } if (!ec) { async_write(socket, ...); }

connection socket.async_connect(... );

connection socket.async_connect(... );

if (!ec) { socket.async_read_some(...); } if (!ec) { async_write(socket, ...); }

if (!ec) { socket.async_read_some(...); } if (!ec) { async_write(socket, ...); }

Threads

socket.async_connect(...);

if (!ec) { socket.async_read_some(...); } if (!ec) { async_write(socket, ...); }

Threads acceptor.async_accept(socket1, ...);

if (!ec) { socket1.async_read_some(...); socket2.async_read_some(...); }

if (!ec) { socket2.async_connect(...); }

if (!ec) { async_write(socket2, ...); } if (!ec) { async_write(socket1, ...); } if (!ec) { socket2.async_read_some(...); }

if (!ec) { socket1.async_read_some(...); }

Threads

class connection : enable_shared_from_this { tcp::socket socket_; vector data_; asio::io_service::strand strand_; // ... void start_write() { async_write(socket_, asio::buffer(data_), strand_.wrap( bind(&connection::handle_write, shared_from_this(), _1, _2))); } // ... };

Threads

class connection : enable_shared_from_this { // ... asio::io_service::strand strand_; // ... void do_foobar(...args...) { ... } public: void foobar(...args...) { strand_.post( bind(&connection::do_foobar, shared_from_this(), ...args...)); } // ... };

Threads

class connection : enable_shared_from_this { // ... asio::io_service::strand strand_; // ... void do_foobar(...args...) { ... } public: void foobar(...args...) { strand_.dispatch( bind(&connection::do_foobar, shared_from_this(), ...args...)); } // ... };

Threads acceptor.async_accept(socket1, ...);

if (!ec) { socket1.async_read_some(...); socket2.async_read_some(...); }

if (!ec) { socket2.async_connect(...); }

if (!ec) { async_write(socket2, ...); } if (!ec) { async_write(socket1, ...); } if (!ec) { socket2.async_read_some(...); }

if (!ec) { socket1.async_read_some(...); }

Threads acceptor.async_accept(socket1, ...);

if (!ec) { socket1.async_read_some(...); socket2.async_read_some(...); }

if (!ec) { socket2.async_connect(...); }

if (!ec) { async_write(socket2, ...); } if (!ec) { async_write(socket1, ...); } if (!ec) { socket2.async_read_some(...); }

if (!ec) { socket1.async_read_some(...); }

Threads acceptor.async_accept(socket1, ...); if (!ec) { socket2.async_connect(...); }

if (!ec) { socket1.async_read_some(...); socket2.async_read_some(...); } if (!ec) {

socket2.async_write_some(...)

if (!ec) {

}

socket1.async_write_some(...)

} if (!ec) { socket2.async_read_some(...); }

if (!ec) { socket1.async_read_some(...); }

Threads

template struct mutex_wrapper { mutex& mutex_; Handler handler_; mutex_wrapper(mutex& m, Handler h) : mutex_(m), handler_(h) {} void operator()() { handler_(); } template void operator()(Arg1 a1) { handler_(a1); } template void operator()(Arg1 a1, Arg2 a2) { handler_(a1, a2); } };

Threads

template mutex_wrapper wrap(mutex& m, Handler h) { return mutex_wrapper(m, h); }

Threads

template void asio_handler_invoke( Function f, mutex_wrapper* w) { mutex::scoped_lock lock(w->mutex_); f(); }

Invocation hook

Threads

class connection : enable_shared_from_this { tcp::socket socket_; vector data_; mutex mutex_; // ... void start_write() { async_write(socket_, asio::buffer(data_), wrap(mutex_, bind(&connection::handle_write, shared_from_this(), _1, _2))); } // ... };

Managing Complexity

Managing Complexity

Approaches: ●

Pass the buck



The buck stops here

Managing Complexity

Approaches: ●



Pass the buck ●

Functions



Classes

The buck stops here ●

Classes

Managing Complexity acceptor.async_accept(socket1, ...);

if (!ec) { socket1.async_read_some(...); socket2.async_read_some(...); }

if (!ec) { socket2.async_connect(...); }

if (!ec) { async_write(socket2, ...); } if (!ec) { async_write(socket1, ...); } if (!ec) { socket2.async_read_some(...); }

if (!ec) { socket1.async_read_some(...); }

Managing Complexity

A “pass the buck” function: template void async_transfer( tcp::socket& socket1, tcp::socket& socket2, asio::mutable_buffers_1 working_buffer, Handler handler); Also known as a “composed operation”

Managing Complexity

Initiating function

if (!ec) { socket1.async_read_some(...); } else { handler(ec); } if (!ec) { async_write(socket2, ...); } else { handler(ec); }

Managing Complexity

template void do_read( tcp::socket& socket1, tcp::socket& socket2, asio::mutable_buffers_1 working_buffer, tuple handler, const error_code& ec) { if (!ec) { socket1.async_read_some( working_buffer, bind(&do_write, ref(socket1), ref(socket2), working_buffer, handler, _1, _2)); } else { get<0>(handler)(ec); } }

Managing Complexity

template void do_write( tcp::socket& socket1, tcp::socket& socket2, asio::mutable_buffers_1 working_buffer, tuple handler, const error_code& ec, size_t length) { if (!ec) { asio::async_write(socket2, asio::buffer(working_buffer, length), bind(&do_read, ref(socket1), ref(socket2), working_buffer, handler, _1)); } else { get<0>(handler)(ec); } }

Managing Complexity

template void async_transfer( tcp::socket& socket1, tcp::socket& socket2, asio::mutable_buffers_1 working_buffer, Handler handler) { do_read( socket1, socket2, working_buffer, make_tuple(handler), error_code()); }

Managing Complexity

Initiating function

if (!ec) { if (do_read) { do_read = false; socket1.async_read_some(...); } else { do_read = true; async_write(socket2, ...); } } else { handler(ec); }

Managing Complexity

template struct transfer_op { bool do_read; tcp::socket& socket1; tcp::socket& socket2; asio::mutable_buffer working_buffer; Handler handler; void operator()(const error_code& ec, size_t length); };

Managing Complexity

template void transfer_op::operator()(const error_code& ec, size_t length) { if (!ec) { if (do_read) { do_read = false; socket1.async_read_some(working_buffer, *this); } else { do_read = true; asio::async_write(socket2, asio::buffer(working_buffer, length), *this); } } else { handler(ec); } };

Managing Complexity

template void async_transfer( tcp::socket& socket1, tcp::socket& socket2, asio::mutable_buffers_1 working_buffer, Handler handler) { transfer_op op = { true, socket1, socket2, working_buffer, handler }; op(error_code(), 0); }

Managing Complexity

acceptor.async_accept(socket1, ...);

if (!ec) { socket2.async_connect(...); }

if (!ec) { async_transfer(socket1, socket2, buffer1, ...); async_transfer(socket2, socket1, buffer2, ...); }

...

...

Managing Complexity

template void asio_handler_invoke(const Function& f, transfer_op* op) { using boost::asio::asio_handler_invoke; asio_handler_invoke(f, addressof(op->handler)); }

Invocation hook

Managing Complexity

template void* asio_handler_allocate(size_t n, transfer_op* op) { using boost::asio::asio_handler_allocate; return asio_handler_allocate(n, addressof(op->handler)); }

Allocation hooks

template void asio_handler_deallocate(void* p, size_t n, transfer_op* op) { using boost::asio::asio_handler_deallocate; asio_handler_deallocate(p, n, addressof(op->handler)); }

Managing Complexity

template void async_transfer( Stream1& stream1, Stream2& stream2, asio::mutable_buffers_1 working_buffer, Handler handler);

Managing Complexity

acceptor.async_accept(socket1, ...);

if (!ec) { socket2.async_connect(...); }

if (!ec) { async_transfer(socket1, socket2, buffer1, ...); async_transfer(socket2, socket1, buffer2, ...); }

...

...

Managing Complexity

A “pass the buck” class: template class proxy { Stream1 up_; Stream2 down_; vector buffer1_, buffer2_; public: proxy(...); Stream1& up() { return up_; } Stream2& down() { return down_; } template void async_run_upstream(Handler handler);

};

template void async_run_downstream(Handler handler);

Managing Complexity

acceptor.async_accept(proxy.down(), ...);

if (!ec) { proxy.up().async_connect(...); }

if (!ec) { proxy.async_run_downstream(...); proxy.async_run_upstream(...); }

...

...

Managing Complexity

acceptor.async_accept(proxy.down(), ...);

if (!ec) { proxy.up().async_connect(...); }

if (!ec) { proxy.async_run_downstream(...); proxy.async_run_upstream(...); }

...

...

Caller must guarantee object lifetime until all operations complete

Managing Complexity

Alternative “pass the buck” class: template class proxy { Stream1 up_; Stream2 down_; vector buffer1_, buffer2_; public: proxy(...); Stream1& up() { return up_; } Stream2& down() { return down_; }

};

template void async_run(Handler handler);

Managing Complexity

acceptor.async_accept(proxy.down(), ...);

if (!ec) { proxy.up().async_connect(...); }

if (!ec) { proxy.async_run(...); }

...

Managing Complexity

Managing Complexity

Managing Complexity

The buck stops here: template class proxy : enable_shared_from_this > { asio::io_service strand_; Stream1 up_; Stream2 down_; vector buffer1_, buffer2_; void do_start(); void handle_transfer(const error_code& ec); public: proxy(...); Stream1& up() { return up_; } Stream2& down() { return down_; } };

void start();

Managing Complexity

template void proxy::start() { strand_.dispatch( bind(&proxy::do_start, this->shared_from_this())); } template void proxy::do_start() { async_transfer(stream1_, stream2_, buffer1_, strand_.wrap( bind(&proxy::handle_transfer, this->shared_from_this(), _1))); async_transfer(stream2_, stream1_, buffer2_, strand_.wrap( bind(&proxy::handle_transfer, this->shared_from_this(), _1))); }

Managing Complexity

Remind you of anything? class connection : enable_shared_from_this { // ... void start(); void stop(); bool is_stopped() const; // ... };

Managing Complexity

The buck stops here

Passing the buck

Operations on I/O Objects

Managing Complexity

int main { asio::io_service io_service; connection conn(io_service);

}

io_service.run();

=

+

No reference counting All memory committed up front Possibility of zero allocations in steady state

Summary

Summary

Challenges: ●

Object lifetimes



Thinking asynchronously



Threads



Managing complexity

Summary

Guidelines: ● ●

Know your object lifetime rules Assume asynchronous change, but know what's under your control



Prefer to keep your logic single-threaded



Pass the buck as often as you can

Thinking Asynchronously: Designing Applications with Boost ... - GitHub

template <class Handler> void do_write( tcp::socket& socket1, tcp::socket& socket2, asio::mutable_buffers_1 working_buffer, tuple handler,.

257KB Sizes 4 Downloads 204 Views

Recommend Documents

Designing Mobile Persuasion: Using Pervasive Applications ... - GitHub
Keywords: Mobile social media, design, persuasion, climate change, transportation ... Transportation, together with food and shelter, is one of the biggest carbon ...

Developing Scientific Applications with Loosely-Coupled ... - GitHub
application developer to focus on supporting the application characteristics and ... Jobs. Dist. Application Patterns / Usage Modes. Distributed Applications.

Thinking Purple - GitHub
Page 24 .... blog/2016/06/23/penetration-testing-vs-red-teaming- · the-age-old-debate-of-pirates-vs-ninja- ... http://www.dtic.mil/doctrine/notes/jdn1_16.pdf.

Architecting Modern Web Applications with ASP.NET Core ... - GitHub
More and more organizations are choosing to host their web applications in the cloud ... NET Core applications to best take advantage of these capabilities. ..... 10. Chapter 3. Choosing Between Traditional Web Apps and SPAs. Decision table ...

A review of C++ 11/14 only Boost libraries - GitHub
1. 1. 1. Boost.Hana. Louis Dionne. 14 none. 2015-04. 1. 0.9. 0.6 header only. 1. 1 ... standalone ASIO the Networking TS reference impl ..... service design and .

Designing and Maintaining Software (DAMS) - GitHub
ASTs are tree data structures that can be analysed for meaning (following JLJ in SYAC 2014/15) ... More Cohesive. Avoids Duplication. Clearer. More Extensible.

Designing and Maintaining Software (DAMS) - GitHub
Open-source. Influenced by Perl, Smalltalk, Eiffel, Ada and Lisp. Dynamic. Purely object-oriented. Some elements of functional programming. Duck-typed class Numeric def plus(x) self.+(x) end end y = 5.plus(6) https://www.ruby-lang.org/en/about · http

Designing and Maintaining Software (DAMS) - GitHub
Automatically detect similar fragments of code. class StuffedCrust def title. "Stuffed Crust " +. @toppings.title +. " Pizza" end def cost. @toppings.cost + 6 end end class DeepPan def title. "Deep Pan " +. @ingredients.title +. " Pizza" end def cost

Designing and Maintaining Software (DAMS) - GitHub
Ruby Testing Frameworks. 3 popular options are: RSpec, Minitest and Test::Unit. We'll use RSpec, as it has the most comprehensive docs. Introductory videos are at: http://rspec.info ...

Designing and Maintaining Software (DAMS) - GitHub
Clear Names. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Naming is hard. “There are only two hard things in Computer. Science: cache invalidation and naming things.” - Phil Karlton http://martinfowler.com/bliki/TwoHardThings.ht

Designing and Maintaining Software (DAMS) - GitHub
Coupling Between Objects. Counts the number of other classes to which a class is coupled (other than via inheritance). CBO(c) = |d ∈ C - (1cl U Ancestors(C))| uses(c, d) V uses(d, c). - Chidamber and Kemerer. A metrics suite for object-oriented des

Designing and Maintaining Software (DAMS) - GitHub
Reducing duplication. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Tactics. Accentuate similarities to find differences. Favour composition over inheritance. Know when to reach for advanced tools. (metaprogramming, code generation).

Designing and Maintaining Software (DAMS) - GitHub
Plug-ins. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Problem. Page 3. Current Architecture. Shareable. Likeable. Food. Pizza. Liking and sharing foods are primary business concerns, so shouldn't be implemented as delegators. Page

Designing and Maintaining Software (DAMS) - GitHub
When we are testing the way that a unit behaves when a condition is met, use a stub to setup the condition. Solution: use stubs for queries class Subscription ... def bill(amount) unless payments.exists(subscription_id: id) payments.charge(subscripti

Designing and Maintaining Software (DAMS) - GitHub
Getting Cohesion. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Single Responsibility. Principle. A class should have only one reason to change. - Martin and Martin. Chapter 8, Agile Principles, Patterns and Practices in C#, Prentice

Designing and Maintaining Software (DAMS) - GitHub
Size != Complexity. “Imagine a small (50 line) program comprising. 25 consecutive "IF THEN" constructs. Such a program could have as many as 33.5 million distinct control paths.” - Thomas J. McCabe. IEEE Transactions on Software Engineering, 2:4,

Designing and Maintaining Software (DAMS) - GitHub
Page 1. Getting Lean. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Lean software… Has no extra parts. Solves the problem at hand and no more. Is often easier to change (i.e., is more habitable). Page 3. The Advice I Want to Give.