Monday 25 February 2019

Is Spring Reactive already obsolete? Inversion of Thread Coupling

Beyond Spring's Dependency Injection only solving 1/5 of the Inversion of Control problem, Spring Reactive bases itself on the event loop. While there are other popular event loop driven solutions (NodeJS, Nginx), the single threaded event loop is a pendulum swing in the other direction from thread-per-request (thread pools). With event loops competing against thread-per-request is there not some pattern that underlies both of them? Well, actually yes!

But before getting to this, let's look at the issues regarding event loops and thread-per-request. If you are more interested in the solution, you can skip the next two sections.

Thread coupling problems

Event loop

First of all, "thread coupling"? Why is this a concern? Well for event loops the single threaded nature requires all I/O to be undertaken asynchronously. Should a database or HTTP call need to block, it will block the single event loop thread and hold up the system. This restriction is in itself a big coupling problem, as to go Reactive all your I/O is coupled to now go asynchronous. This means no more ORMs like JPA to make access to databases easier (as JPA requires blocking database calls). Yep, something that used to remove 40-60% of boiler plate code in applications is now not unusable (enjoy writing this all over again!)

Beyond the restrictive I/O in your decision to use Reactive patterns, the ability to use multiple processors is restricted as there is only one thread. Ok, instances of the Reactive engine are duplicated to each CPU, however they can not share state. The multi-threaded implications of sharing state between two event loops is difficult. Reactive programming is hard enough, let alone adding multi-threading into it. Yes, communication between event loops can be via events. However, using this to keep duplicated copies of shared state in sync across event loops creates problems that are just avoided. Basically you are told to design your Reactive systems to avoid this with immutability.

Therefore, you are stuck coupled to the one thread. So what? Well if you have computationally expensive operations, such as security cryptography (JWT), it creates scheduling problems. By being on a single thread, this operation must be completed before anything else can be undertaken. With multiple threads, other threads can be time sliced in by the operating system to progress other less CPU intensive requests. However, you only have the one thread so all that lovely operating system thread scheduling is now lost. You're stuck waiting for the expensive CPU intensive operations to complete before servicing anything else.

Oh please, just ignore these problems! We developers like performance. Reactive is all in the aim of greater performance and improved scalability. Lesser threads allows reduced overheads to allow improved throughput. Ok, yes, I'll have better performing production systems potentially reducing hardware costs. However, it's going to be a lot slower to build and enhance that production system due to coupling restrictions that comes from single threaded event loops. Not to mention, having to rewrite algorithms to not hog the CPU. Given the scarcity of developers compared to the over abundant supply of cloud hardware, arguing about costs of scale may only be for those rare significantly large systems.

We do loose a lot going Reactive. This is possibly to the point that we have not thought it through enough. Hence, possibly why Reactive frameworks warn against changing to it whole sale. They usually indicate Reactive patterns only work for smaller less complicated systems.

Thread-per-request (thread pools)

On the flip side, thread-per-request patterns (such as Servlet 2.x) use thread pools to handle scale. They assign a thread to service the request and scale out by having multiple (typically pooled) threads.

We can probably read many articles touting Reactive over the scale limitations of thread-per-request, but the main issue with thread-per-request is not actually in performance nor scale. The issue with thread-per-request is lot more permissive to your application and can actually pollute your whole architecture.

To see this problem, just look at invoking a method:

Response result = object.method(identifier);

Should the implementation of the method be as follows:

@Inject Connection connection;
@Inject HttpClient client;

public Result method(Long identifier) {

  // Retrieve synchronous database result
  ResultSet resultSet = connection.createStatement()
    .executeQuery("<some SQL> where id = " + identifier);
  resultSet.next();
  String databaseValue = resultSet.getString("value");

  // Retrieve synchronous HTTP result
  HttpResponse response = client.send("<some URL>/" + databaseValue);

  // Return result requiring synchronous results to complete
  return new Result(response.getEntity());
}

This creates a coupling problem to the thread of the request, that can pollute out to your whole architecture. Yes, you've just placed a coupling on the request thread out to your other systems.

While the database call is synchronous, the HTTP call is also forcing the downstream system to respond synchronously. We can't change the HTTP call to be asynchronous, because the request thread wants to continue with a result to return from the method. This synchronous coupling to the request thread not only limits the call, but also limits the downstream system to have to provide a synchronous response. Hence, the thread-per-request thread coupling can pollute out to your other systems and possibly across your entire architecture. No wonder the REST micro-service pattern of synchronous HTTP calls are so popular! It is a pattern that forces itself top down on your system. Sounds like thread-per-request and Reactive share this same opinion on forcing everything top down to support themselves.

Threading to support I/O

In summary, the problems are as follows.

Single threaded event loops:
  • couple you to asynchronous communication only (simple JPA code is no longer available)
  • just avoids multi-threading, as two threads executing events from the event queue would create considerable synchronisation problems (likely slowing solution and causing concurrency bugs that are hard to code against for the best of developers)
  • loose the advantage of the thread scheduling that operating systems have spent considerable effort optimising
While thread-per-request solutions:
  • couples you to synchronous communication only (as the result is expected immediately; and not some time later via callback)
  • have higher overheads (to single thread event loops) due to managing more threads and therefore less scalable
The pendulum swing between thread pools and Reactive single threaded can actually be considered going from synchronous communication (thread-per-request) to asynchronous communication (single threaded event loops). The remaining problems are actually implementation constraints of a threading model built specifically to support each type of communication. Plus given the coupling on downstream systems that synchronous communication poses, this pendulum swing to asynchronous communication is not all a bad thing.

So the question is, why are we forced to choose only one communication style? Why can't we use synchronous and asynchronous communication styles together?

Well, we can't put asynchronous calls inside synchronous method calls. There is no opportunity for callbacks. Yes, we can block waiting on the callback but Reactive will consider itself superior in scale due to additional threading overheads involved in this. Therefore, we need asynchronous code to allow synchronous calls.

However, we can't put synchronous calls inside event loops, as it halts the event loop thread. Hence, we need extra threads to undertake the synchronous calls to allow the event loop thread to carry on with other events.

Reactive has the answer. Use a Scheduler:

Mono blockingWrapper = Mono.fromCallable(() -> {
  return /* make a remote synchronous call */
}).subscribeOn(Schedulers.elastic());


Yay, now we can do synchronous calls within the event loop. Problem solved (well sort of).

Well it's sorted if you can trust that you properly wrapped all synchronous calls in Callables. Get one wrong, and well you are blocking your event loop thread and halting your application. At least in multi-threaded applications only the particular request suffered, not the whole application.

This seems, to me anyway, more a work around than an actual solution to the problem. Oh wait, everything needs to be Reactive top down so that solves this problem. Just don't do blocking calls and change all your drivers and your whole technology stack to Reactive. The whole "change everything to suit us, in a way that only integrates with us" seems very close to technology vendor lock in - in my opinion anyway.

Therefore, can we consider a solution that allows synchronous calls and does not rely so heavily on the developer getting it right? Why, yes!

Inverting the Thread Coupling

The asynchronous communication driven Reactive single threaded event loop (excuse the mouth full) is identified as the right solution. Synchronous communication is solved by developers using Schedulers. In both cases, the Reactive functions are run with a thread dictated for them:
  • asynchronous functions are executed with the thread of the event loop
  • synchronous functions executed with thread from the Scheduler
The control of the function's executing thread is heavily dependent on the developer getting it right. The developer has enough on their plate focusing on building code to meet feature requirements. Now the developer is intimately involved in the threading of the application (something thread-per-request always somewhat abstracted away from the developer). This intimacy to threading significantly increases the learning curve for building anything Reactive. Plus it will have the developer loose a lot of hair when they pull it out at 2 am trying to get the code working for that deadline or production fix.

So can we remove the developer from having to get the threading right? Or more importantly, where do we give control of selecting the thread?

Let's look at a simple event loop:

public interface AsynchronousFunction {
  void run();
}

public void eventLoop() {
  for (;;) {
    AsynchronousFunction function = getNextFunction();
    function.run();
  }
}

Well, the only thing we can target for control is the asynchronous function itself. Using an Executor to specify the thread, we can enhance the event loop as follows:

public interface AsynchronousFunction {
  Executor getExecutor();
  void run();
}

public void eventLoop() {
  for (;;) {
    AsynchronousFunction function = getNextFunction();
    function.getExecutor().execute(() -> function.run());
  }
}

This now allows the asynchronous function to specify its required threading, as:
  • using the event loop thread is via a synchronous Executor: getExecutor() { return (runnable) -> runnable.run(); }
  • using separate thread for synchronous calls is via Executor backed by thread pool: getExecutor() { return Executors.newCachedThreadPool(); }
Control is inverted so that the developer is no longer responsible for specifying the thread. The function now specifies the thread for executing itself.

But how do we associate an Executor to a function?

We use the ManagedFunction of Inversion of Control:

public interface ManagedFunction {
  void run();
}

public class ManagedFunctionImpl
    implements ManagedFunction, AynchronousFunction {

  @Inject P1 p1;
  @Inject P2 p2;
  @Inject Executor executor;

  @Override
  public void run() {
    executor.execute(() -> implementation(p1, p2));
  }

  private void implementation(P1 p1, P2 p2) {
    // Use injected objects for functionality
  }
}

Note that only the relevant ManagedFunction details have been included. Please see Inversion of (Coupling) Control for more details of the ManagedFunction.

By using the ManagedFunction, we can associate an Executor to each function for the enhanced event loop. (Actually, we can go back to the original event loop, as the Executor is encapsulated within the ManagedFunction).

So now the developer is no longer required to use Schedulers, as the ManagedFunction takes care of which thread to use for executing the function's logic.

But this just moves the problem of the developer getting it right from code to configuration. How can we make it possible to reduce developer error in specifying the correct thread (Executor) for the function?

Deciding the executing thread

One property of the ManagedFunction is that all objects are Dependency Injected. Unless Dependency Injected, there are no references to other aspects of the system (and static references are highly discouraged). Hence, the Dependency Injection meta-data of the ManagedFunction provides details of all the objects used by the ManagedFunction.

Knowing the objects used by a function helps in determining the asynchronous/synchronous nature of the function. To use JPA with the database a Connection (or DataSource) object is required. To make synchronous calls to micro-services a HttpClient object is required. Should none of these be required by the ManagedFunction, it is likely safe to consider no blocking communication is being undertaken. In other words, if the ManagedFunction does not have a HttpClient injected, it can't make HttpClient synchronous blocking calls. The ManagedFunction is, therefore, safe to be executed by the event loop thread and not halt the whole application.

We can, therefore, identify a set of dependencies that indicate if the ManagedFunction requires execution by a separate thread pool. As we know all dependencies in the system, we can categorise them as asynchronous/synchronous. Or more appropriately, whether the dependency is safe to use on the event loop thread. If the dependency is not safe, then the ManagedFunctions requiring that dependency are executed by a separate thread pool. But what thread pool?

Do we just use a single thread pool? Well, Reactive Schedulers give the flexibility to use / re-use varying thread pools for the various functions involving blocking calls. Hence, we need similar flexibility in using multiple thread pools.

We use multiple thread pools by mapping thread pools to dependencies. Ok, this is a little bit to get your head around. So let's illustrate with an example:

public class ManagedFunctionOne implements ManagedFunction {
  // No dependencies
  // ... remaining omitted for brevity
}

public class ManagedFunctionTwo implements ManagedFunction {
  @Inject InMemoryCache cache;
  // ...
}

public class ManagedFunctionThree implements ManagedFunction {
  @Inject HttpClient client;
  // ...
}

public class ManagedFunctionFour implements ManagedFunction {
  @Inject EntityManager entityManager;
  // meta-data also indicates transitive dependency on Connection
  // ...
}

Now, we have the thread configuration as follows:

DependencyThread Pool
HttpClientThread Pool One
ConnectionThread Pool Two

We then use the dependencies to map ManagedFunctions to Thread Pools:

ManagedFunctionDependencyExecutor
ManagedFunctionOne,
ManagedFunctionTwo
(none in thread pool table)Event Loop Thread
ManagedFunctionThreeHttpClientThread Pool One
ManagedFunctionFourConnection (as transitive dependency of EntityManager)Thread Pool Two

The decision of the thread pool (Executor) to use for the ManagedFunction is now just mapping configuration. Should a dependency invoke blocking calls, it is added to the thread pool mappings. The ManagedFunction using this dependency will no longer be executed on the event thread loop, avoiding the application halting.

Furthermore, the likelihood of missing blocking calls is significantly reduced. As it is relatively easy to categorise the dependencies, it leaves less chance of missing blocking calls. Plus if a dependency is missed, it is only a configuration change to the thread pool mappings. It is fixed without code changes. Something especially useful as the application grows and evolves. This is unlike Reactive Schedulers that require code changes and significant thought by the developer.

As the executing thread to execute a ManagedFunction is now controlled by the framework (not the application code), it effectively inverts control of the executing thread. No longer does the developer code threading. The framework configures it based on the dependency characteristics of the ManagedFunctions.

OfficeFloor

This is all good in theory, but show me the working code!

OfficeFloor (http://officefloor.net) is an implementation of the inversion of thread control patterns discussed in this article. We find frameworks are too rigid with their threading models that causes work arounds, such as Reactive Schedulers. We are looking for the underlying patterns to create a framework that does not require such work arounds. Code examples can be found in the tutorials and we value all feedback.

Note that while OfficeFloor follows inversion of thread control, it's actual threading model is more complex to take other aspects into consideration (e.g. dependency context, mutating state, thread locals, thread affinity, back pressure and reduced locking to increase performance). These, however, are topics for other articles. But, as this article highlights, the threading for OfficeFloor applications is a simple configuration file based on dependency mappings.

Conclusion

Inversion of control for the thread allows the function to specify it's own thread. As the thread is controlled by the injected Executor, this pattern is named Thread Injection. By allowing the injection, the choice of thread is determined by configuration rather than code. This relieves the developer of the potentially error prone, buggy task of coding threading into applications.

The side benefit of Thread Injection is that thread mapping configurations can be tailored to the machine the application is running on. On a machine with many CPUs, more thread pools can be configured to take advantage of thread scheduling by the operating system. On smaller machines (e.g. embedded) there can be more re-use of thread pools (potentially even none for single purpose applications that can tollerate blocking to keep thread counts down). This would involve no code changes to your application, just configuration changes.

Furthermore, computationally expensive functions that may tie up the event loop can also be moved to a separate thread pool. Just add in a dependency for this computation to the thread pool mappings and all ManagedFunctions undertaking the computation are now not holding up the event loop thread. The flexibility of Thread Injection is beyond just supporting synchronous/asynchronous communication.

As Thread Injection is all driven from configuration, it does not require code changes. It actually does not require any threading coding by the developer at all. This is something Reactive Schedulers are incapable of providing.

So the question is, do you want to tie yourself to the single threaded event loop that really is just a single purpose implementation for asynchronous I/O? Or do you want to use something a lot more flexible?

Inversion of (Coupling) Control

The question of "What is Inversion of Control?" or "What is Dependency Injection?" is met with code examples, vague explanations and even on StackOverflow identified as low quality answers - https://stackoverflow.com/questions/3058/what-is-inversion-of-control

We use inversion of control and dependency injection and even push it as the correct way to build applications. Yet, we can not clearly articulate why!!!

The reason is we have not clearly identified what "control" is. Once we understand what we are inverting, the concept of Inversion of Control vs Dependency Injection is not actually the question to be asked. It actually becomes the following:

Inversion of Control = Dependency (state) Injection + Thread Injection + Continuation (function) Injection

To explain this, well, let's do some code. (And yes, the apparent problem of using code to explain Inversion of Control is repeating, but bear with me - the answer has always been right before your eyes).

One clear use of Inversion of Control / Dependency Injection is the repository pattern to avoid passing around a connection. Instead of the following:

public class NoDependencyInjectionRepository implements Repository<Entity> {
  public void save(Entity entity, Connection connection) throws SQLException {
    // Use connection to save entity to database
  }
}

Dependency Injection allows the repository to be re-implemented as:

public class DependencyInjectionRepository implements Repository<Entity> {
  @Inject Connection connection;
  public void save(Entity entity) throws SQLException {
    // Use injected connection to save entity to database
  }
}

Now, do you see the problem it just solved?

If you are thinking, I can now change the Connection to say REST calls and this is all flexible to change. Well, you would be close.

To see the problem it solved, do not look at the implementation. Look at the interface. The client calling code has gone from:

repository.save(entity, connection);

to the following:

repository.save(entity);

We have removed the coupling of the client code to provide a connection on calling the method. By removing the coupling, we can substitute a different implementation of the repository (again, boring old news, but bear with me):

public class WebServiceRepository implements Repository<Entity> {
  @Inject WebClient client;
  public void save(Entity entity) {
    // Use injected web client to save entity
  }
}

With the client able to continue to call the method just the same:

repository.save(entity);

The client is unaware that the repository is now calling a micro-service to save the entity, rather than talking directly to a database. (Actually, the client is aware but we will come to that shortly.)

So taking this to an abstract level regarding the method:

  R method(P1 p1, P2 p2) throws E1, E2

  // with dependency injection becomes
  @Inject P1 p1;
  @Inject P2 p2;
  R method() throws E1, E2

The coupling of the client to provide arguments to the method is removed by Dependency Injection.



Now, do you see the four other problems of coupling?



At this point, I warn you that you will never look at code the same again once I show you the coupling problems. This is the point in the Matrix where I ask you if you want to take the red or blue pill, because there is no going back once I show you how far down the rabbit hole this problem really is - say that refactoring is actually not necessary and there are issues in the fundamentals of modelling logic in computer science (ok, big statement but read on and I can't put it any other way).



So you chose the red pill.



Let's prepare you.



To identify the four extra coupling problems, let's look at the abstract method again:

  @Inject P1 p1;
  @Inject P2 p2;
  R method() throws E1, E2

  // and invoking it
  try {
    R result = object.method();
  } catch (E1 | E2 ex) {
    // handle exception
  }

What is coupled by the client code?
  • the return type
  • the method name
  • the handling of exceptions
  • the thread provided to the method
Dependency Injection allowed me to change the objects required by the method without changing the client code calling the method. However, if I want to change my implementing method by:
  • changing it's return type
  • changing it's name
  • throwing a new exception (in the above case of swapping to a micro-service repository, throwing a HTTP exception rather than a SQL exception)
  • using a different thread (pool) to execute the method than the thread provided by the client call
This involves "refactoring" all client code for my method. Why should the caller dictate the coupling when the implementation has the hard job of actually doing the functionality? We should actually invert the coupling so that the implementation can dictate the method signature (not the caller).

This is likely the point you look at me like Neo does in the Matrix going "huh"? Let implementations define their method signatures? But isn't the whole OO principle about overriding and implementing abstract method signature definitions? And that's just chaos because how do I call the method if it's return type, name, exceptions, arguments keep changing as the implementation evolves?

Easy. You already know the patterns. You just have not seen them used together where their sum becomes a lot more powerful than their parts.

So let's walk through the five coupling points (return type, method name, arguments, exceptions, invoking thread) of the method and decouple them.

We have already seen Dependency Injection remove the argument coupling by the client, so one down.

Next let's tackle the method name.

Method Name Decoupling

Many languages, including Java with lambda's, are allowing/have functions as first class citizens of the language. By creating a function reference to a method, we no longer need to know the method name to invoke the method:

Runnable f1 = () -> object.method();
// Client call now decoupled from method name
f1.run()

We can even now pass different implementation of the method around with Dependency Injection:

@Inject Runnable f1;
void clientCode() {
  f1.run(); // to invoke the injected method
}

Ok, a bit of extra code for not much value. But again bear with me. We have decoupled the method's name from the caller.

Next let's tackle the exceptions from the method.

Method Exceptions Decoupling

By using the above technique of injecting functions, we inject functions to handle exceptions:

Runnable f1 = () -> {
  @Inject Consumer<E1> h1;
  @Inject Consumer<E2> h2;
  try {
    object.method();
  } catch (E1 e1) {
    h1.accept(e1);
  } catch (E2 e2) {
    h2.accept(e2);
  }
}
// Note: above is abstract pseudo code to identify the concept (and we will get to compiling code shortly)

Now exceptions are no longer the client caller's problem. Injected methods now handle the exceptions decoupling the caller from having to handle exceptions.

Next let's tackle the invoking thread.

Method's Invoking Thread Decoupling

By using an asynchronous function signature and injecting an Executor, we can decouple the thread invoking the implementing method from that provided by the caller:

Runnable f1 = () -> {
  @Inject Executor executor;
  executor.execute(() -> {
    object.method();
  });
}

By injecting the appropriate Executor, we can have the implementing method invoked by any thread pool we require. To re-use the client's invoking thread we just use a synchronous Exectutor:

Executor synchronous = (runnable) -> runnable.run();

So now we can decouple thread to execute the implementing method from the calling code's thread.

But with no return value, how do we pass state (objects) between methods? Let's combine it all together with Dependency Injection.

Inversion of (Coupling) Control

Let's combine the above patterns together with Dependency Injection to get the ManagedFunction:

public interface ManagedFunction {
  void run();
}

public class ManagedFunctionImpl implements ManagedFunction {

  @Inject P1 p1;
  @Inject P2 p2;
  @Inject ManagedFunction f1; // other method implementations to invoke
  @Inject ManagedFunction f2;
  @Inject Consumer<E1> h1;
  @Inject Consumer<E2> h2;
  @Inject Executor executor;

  @Override
  public void run() {
    executor.execute(() -> {
      try {
        implementation(p1, p2, f1, f2);
      } catch (E1 e1) {
        h1.accept(e1);
      } catch (E2 e2) {
        h2.accept(e2);
      });
  }

  private void implementation(
    P1 p1, P2 p2,
    ManagedFunction f1, ManagedFunction f2
  ) throws E1, E2 {
    // use dependency inject objects p1, p2
    // invoke other methods via f1, f2
    // allow throwing exceptions E1, E2
  }
}

Ok, there's a lot going on here but it's just the patterns above combined together. The client code is now completely decoupled from the method implementation, as it just runs:

@Inject ManagedFunction function;
public void clientCode() {
  function.run();
}

The implementing method is now free to change without impacting the client calling code:
  • there is no return type from methods (slight restriction always being void, however necessary for asynchronous code)
  • the implementing method name may change, as it is wrapped by the ManagedFunction.run()
  • parameters are no longer required by the ManagedFunction. These are Dependency Injected, allowing the implementing method to select which parameters (objects) it requires
  • exceptions are handled by injected Consumers. The implementing method may now dictate what exceptions it throws, requiring only different Consumers injected. The client calling code is unaware that the implementing method may now be throwing a HTTPException instead of SQLException. Furthermore, Consumers can actually be implemented by ManagedFunctions injecting the Exception.
  • the injection of the Executor allows the implementing method to dictate it's thread of execution by specifying the Executor to inject. This could result in re-using the client's calling thread or have the implementation run by a separate thread or thread pool

All five coupling points of the method by it's caller are now decoupled.

We have actually "Inverted Control of the Coupling". In other words, the client caller no longer dictates what the implementing method can be named, use as parameters, throw as exceptions, which thread to use, etc. Control of coupling is inverted so that the implementing method can dictate what it couples to by specifying it's required injection.

Furthermore, as there is no coupling by the caller there is no need to refactor code. The implementation changes and then configures in it's coupling (injection) to the rest of the system. Client calling code no longer needs to be refactored.

So in effect Dependency Injection only solved 1/5 of the method coupling problem. For something that is so successful for only solving 20% of the problem, it does show how much of a problem coupling of the method really is.

OfficeFloor

Implementing the above patterns would create more code than it's worth in your systems. That's why the OfficeFloor (http://officefloor.net) "true" inversion of control framework has been put together to lessen the burdon of this code. This has been an experiment in the above concepts to see if real systems are easier to build and maintain with "true" inversion of control. Try the tutorials out to see the concepts in action (http://officefloor.net/tutorials). We value your feedback, as we ourselves feel so claustrophobic with the coupling imposed by the method signature.

OfficeFloor is also an implementation of many other innovative patterns that is going to take many more articles to cover. However, the concepts have been published should you like some heavy academic reading (http://doi.acm.org/10.1145/2739011.2739013 with free download available from http://www.officefloor.net/about.html).

Possibly the most interesting aspect of these patterns is that the configuration is no longer in code, but actually done graphically. The follow is an example of this configuration:



Summary

So next time you reach for the Refactor Button/Command, realise that this is brought on by coupling of the method that has been staring us in the face every time we write code.

And really why do we have the method signature? It is because of the thread stack. We need to load memory onto a thread stack, and the method signature follows this behaviour of the computer. However, in the real world modelling of behaviour between objects there is no thread stack. Objects are loosely coupled with very small touch points - not the five coupling aspects imposed by the method.

Furthermore, in computing we strive towards low coupling and high cohesion. One might possibly put forward a case that in comparison to ManagedFunctions, that methods are:

  • high coupling: as methods have five aspects of coupling to the client calling code
  • low cohesion: as the handling of exceptions and return types from methods starts blurring the responsibility of the methods over time. Continuous change and shortcuts can quickly degrade the cohesiveness of the implementation of the method to start handling logic beyond it's responsibility

So given, as a discipline we strive for low coupling and high cohesion, our most fundamental building block (the method and also for that matter the function) may actually go against our most core principles.