答:
The declarative code is harder to debug.

I would say that is a function of the quality of your debugger. If your debugger understands the imperative constructs but not the declarative ones, then of course the delarative ones are harder to debug. But you could easily imagine a different debugger with different priorities, where the opposite is true.

There are some language designers which do care so much about tooling that they are even willing to let toolability influence the language design or even compromise on language features to facilitate good tools. The obvious example is Kotlin, which is designed by a tool vendor (JetBrains). The lead developers of Scala are also famously opposed to expanding Scala's type inference, not because they don't know how to do it (they do) or because it's hard to implement (it is, but they have smart compiler writers), but because they haven't figured out a way to implement it with good error messages. (Think mid-90s C++ template instantiation errors.)

The declarative code is a bit slower. […] With declarative it seems like you can easily lose touch with what your code is actually doing.

Yes. That is the whole point. That's why it's called "declarative": because you declare what you want to happen, not how you want it to happen.

This gives a lot more leeway for the compiler to optimize things.

There's a great example in one of the Supero (a supercompiler for Haskell) papers. The author compares a simple, expressive, declarative, purely functional, one-line word count function in Haskell (main = print . length . words =<< getContents), compiled with a combination of Supero, GHC, and YHC with a hand-optimized state-machine-based while loop in C, and much to his own surprise finds that the Haskell is marginally faster. How could that happen? Well, the compiler actually transformed the Haskell code into the same state-machine loop that the hand-written C version has, but it can do one additional trick that C (at least without inline assembly) can't: encode the state(s) in the program counter.

In your case, you have created a declarative DSL, if you will. But the C++ optimizer doesn't know anything about the semantics of your DSL, so it can't take advantage of the additional freedom.

The declarative code is harder to modify over time, I find. If I want to do some extra operation on each character, I need to add another […] lambda etc. In imperative programming I just add a line of plain ol' code inside the loop […].

I don't follow. Is there really difference between:

```<code>step1();
step2();
step2a(); // inserted later
step3();
</code>```

and

```<code>transform1    |
  transform2  |
  transform2a | // inserted later
  transform3;
</code>```

I find the imperative style more intuitive as I'm writing.

There is no such (absolute) thing as "intuitive". Intuitiveness is all about familiarity. Remember the Star Trek Movie, when Scotty tries to use a computer with what we consider to be an intuitive user interface? He ends up trying to speak voice commands into the mouse.

A lot of people consider loops to be intuitive, and recursion un-intuitive. However, just a couple of months ago, there was a question in the Ruby tag on StackOverflow by a complete programming newbie, who had written code like this:

```<code>def main
  # do something
  main
end
</code>```

To him, this was the intuitive way to do something over and over again. (And why not? "Do something, and then start again what you are doing" is a perfectly sensible mental model for what we imperative guys call a "loop", is it not?) And for a Scheme, ML, or Haskell programmer, this would be intuitive, and loops wouldn't. (In fact, a pure ML or Haskell programmer wouldn't even know what we are talking about, because their languages have no loops.)

Another example from me personally: as a Ruby programmer and fan of Smalltalk, I cannot understand why anyone would ever want a static AOT compiler. And yet, the C++ community cannot understand why anyone would ever want a dynamic JIT compiler.

Unless and until you have written the same amount of (serious, non-toy, complex, large, production-level system) code in both styles, the style which is more familiar will be more "intuitive". That's just the nature of things.

Title: Is declarative programming overrated?

Body:
I've been programming for years with primarily-imperative languages (C++, C#, javascript, python), but have recently experimented with some functional langauges (Lisp, Haskell) and was excited to try applying some of the declarative-style programming ideas in C++. I have a custom range-based STL replacement library I wrote a while back that made a lot of that possible in a fairly clean way.

Here's an example - a function to see if a target substring exists inside of a source string, ignoring case. First the plain-ol' imperative way:

```<code>bool StringContains(const string& source, const string& target) {

    // Figure out search area, exit if target is too big to exist in source
    if (target.size() > source.size()) {
        return false;
    }
    size_t endIndex = source.size() - target.size();

    // For each potential position...
    for (size_t i = 0; i <= endIndex; i++) {

        // Check if target is here
        size_t strPos = i;
        bool foundHere = true;
        for (char targetChar : target) {
            char strChar = tolower(source[strPos]);
            targetChar = tolower(targetChar);
            if (strChar != targetChar) {
                foundHere = false;
                break;
            }
            strPos++;
        }

        // If found here, return true
        if (foundHere) {
            return true;
        }
    }

    // If not found by now, return false
    return false;
}
</code>```

And here it is using my declarative library (which use some C++11 magic):

```<code>bool StringContainsDec(const string& source, const string& target) {

    // Figure out search area, exit if target is too big to exist in source
    if (target.size() > source.size()) {
        return false;
    }
    size_t endIndex = source.size() - target.size();

    // For each potential position...
    auto targetRange = All(target) | Transformed(tolower);
    for (size_t i = 0; i <= endIndex; i++) {

        // If found here, return true
        auto sourceRange = All(source) | Sliced(i, i + target.size()) |
            Transformed(tolower);
        if (RangesMatch(sourceRange, targetRange)) {
            return true;
        }
    }

    // If not found by now, return false
    return false;
}
</code>```

A little more compact and perhaps English-like and readable, which is nice. The "|" is analogous to a shell script pipe, routing values thru to the next operation. So:

```<code>All(source) | Sliced(i, i + target.size()) | Transformed(tolower)
</code>```

means, set up a range that, when iterated, will take each character of 'source', sliced between index i and i + target.size(), and pass each character through tolower().

RangesMatch() iterates each of the two ranges and returns true if each element matches.

So, that's all fine and good, and it works correctly. But over time I've found, experimenting with this approach in practical situations:

The declarative code is harder to debug. With the imperative, you can just step through in the debugger, line by line, and see what's going on. With the declarative, it's not that much more complex, but you need to jump through some different library functions of constructing the range, calling the internal iterator functions (Front(), PopFront(), etc.) etc. So it jumps you around from place to place, making it more confusing to track the logic. I imagine this is easier in e.g. a Lisp debugger.

The declarative code is a bit slower. On my system it's about half the speed of the imperative code. The ranges are lazily constructed and very efficient, and only allocate locals on the stack, but it still involves a little more under the hood, like tracking start/end pointers, which adds up in nested loops etc. With declarative it seems like you can easily lose touch with what your code is actually doing. If you have a huge chain of operations you'll miss opportunities to simplify, save useful intermediate values so they don't need to be recalculated later, etc.

The declarative code is harder to modify over time, I find. If I want to do some extra operation on each character, I need to add another transform function, or lambda etc. In imperative programming I just add a line of plain ol' code inside the loop, or 100 lines if needed, and it's fairly easy to follow.

I find the imperative style more intuitive as I'm writing. It better reflects the order that things happen, let's me proceed step by step without having to juggle the whole thing in my head up front, etc.

Now all this stuff might be particular to my implementation or my preferences, but I imagine some of it is inherent to the style too? This string function is just one example but I've found it with all kinds of things when I implement both side by side - that 80% of the time imperative style wins for me, just do it with plain old loops and if statements rather than messing around with higher-order functions, map/reduce, etc. They may add some code brevity and a little less typing if your text editor sucks, but in complicated real-world situations they become confusing and harder to maintain.

So is declarative overrated? Has anyone had broad experience with both approaches, especially with complex real-world projects in functional languages? Curious to hear what other people think.

Body:
The declarative code is harder to debug.

I would say that is a function of the quality of your debugger. If your debugger understands the imperative constructs but not the declarative ones, then of course the delarative ones are harder to debug. But you could easily imagine a different debugger with different priorities, where the opposite is true.

There are some language designers which do care so much about tooling that they are even willing to let toolability influence the language design or even compromise on language features to facilitate good tools. The obvious example is Kotlin, which is designed by a tool vendor (JetBrains). The lead developers of Scala are also famously opposed to expanding Scala's type inference, not because they don't know how to do it (they do) or because it's hard to implement (it is, but they have smart compiler writers), but because they haven't figured out a way to implement it with good error messages. (Think mid-90s C++ template instantiation errors.)

The declarative code is a bit slower. […] With declarative it seems like you can easily lose touch with what your code is actually doing.

Yes. That is the whole point. That's why it's called "declarative": because you declare what you want to happen, not how you want it to happen.

This gives a lot more leeway for the compiler to optimize things.

There's a great example in one of the Supero (a supercompiler for Haskell) papers. The author compares a simple, expressive, declarative, purely functional, one-line word count function in Haskell (main = print . length . words =<< getContents), compiled with a combination of Supero, GHC, and YHC with a hand-optimized state-machine-based while loop in C, and much to his own surprise finds that the Haskell is marginally faster. How could that happen? Well, the compiler actually transformed the Haskell code into the same state-machine loop that the hand-written C version has, but it can do one additional trick that C (at least without inline assembly) can't: encode the state(s) in the program counter.

In your case, you have created a declarative DSL, if you will. But the C++ optimizer doesn't know anything about the semantics of your DSL, so it can't take advantage of the additional freedom.

The declarative code is harder to modify over time, I find. If I want to do some extra operation on each character, I need to add another […] lambda etc. In imperative programming I just add a line of plain ol' code inside the loop […].

I don't follow. Is there really difference between:

step1();
step2();
step2a(); // inserted later
step3();

and

transform1    |
  transform2  |
  transform2a | // inserted later
  transform3;

I find the imperative style more intuitive as I'm writing.

There is no such (absolute) thing as "intuitive". Intuitiveness is all about familiarity. Remember the Star Trek Movie, when Scotty tries to use a computer with what we consider to be an intuitive user interface? He ends up trying to speak voice commands into the mouse.

A lot of people consider loops to be intuitive, and recursion un-intuitive. However, just a couple of months ago, there was a question in the Ruby tag on StackOverflow by a complete programming newbie, who had written code like this:

def main
  # do something
  main
end

To him, this was the intuitive way to do something over and over again. (And why not? "Do something, and then start again what you are doing" is a perfectly sensible mental model for what we imperative guys call a "loop", is it not?) And for a Scheme, ML, or Haskell programmer, this would be intuitive, and loops wouldn't. (In fact, a pure ML or Haskell programmer wouldn't even know what we are talking about, because their languages have no loops.)

Another example from me personally: as a Ruby programmer and fan of Smalltalk, I cannot understand why anyone would ever want a static AOT compiler. And yet, the C++ community cannot understand why anyone would ever want a dynamic JIT compiler.

Unless and until you have written the same amount of (serious, non-toy, complex, large, production-level system) code in both styles, the style which is more familiar will be more "intuitive". That's just the nature of things.


问:
Implementing "buy one get one for free" coupon logic for shopping cart
Trying to solve bogo coupon logic with proper design pattern, but having trouble identifying one.

Use case: "Buy iPad get SmartCase for free"

Suppose we have the following objects:

```<code>Product:
 - getPrice

CartItem(Product p, quantity):
 - getPrice

Cart:
 - getItems()
 - addItem(CartItem ci)

Coupon(code):
 - getCode

CouponBuyOneGetOneFree(code) extends Coupon: (not sure about inheritance here)
</code>```

1) What design pattern fits here?

2) What if i needed to set up the same logic without coupon but in product settings itself?

I have implemented Decorator pattern for CartItem when discount is applied to the same CartItem by this example and it works great, but still can not achieve the result of this use case.

Somehow I need to check that Ipad and SmartCase both are in the cart and apply discount to SmartCase only. Also if I add another iPad i should get another SmartCase for free.

答:
No design pattern is required beside basic subclassing. You just need a Coupon interface with a single method: calculateDiscount(cart, customer). Create an implementation of Coupon for each different kind of discount, e.g. a FreeAddonCoupon or a LoyaltyCoupon.

So a Cart has CartItems and Coupons. Add the price of the items, subtract the discount for each coupon.

ContentLicense: CC BY-SA 3.0


问:
Why are statements in many programming languages terminated by semicolons?
Is there a reason that a semi-colon was chosen as a line terminator instead of a different symbol?

I want to know the history behind this decision, and hope the answers will lead to insights that may influence future decisions.

答:
Here are two questions: Why ALGOL got semicolon and why other languages went after it.

The first question is already answered in many ways here.

As the second one, The ALGOL was very widely used as a pseudocode language for algorithms writing. So, semicolons soon became natural for users of different languages. And naturally they were taken for the younger languages.


问:
How to implement Singleton on a Resource / ResourcesManager case?
This is a design pattern academic question exercise.
We have a Resource class and a pure fabrication class ResourcesManager to manage objects w/ type Resource.

The question is
Given the following two definitions:

```<code>public class Resource {

}

public class ResourcesManager {

 public static Resource loadFromDb(int resID) {
   ...
 }
 public static Resource createNew(String name) {
    ...
 }
}
</code>```

Apply Singleton pattern and Factory

My solution would be to apply Singleton and Factory both to ResourcesManager class.
Since I need only one instance of the Manager, and it's the one in charge of creating new objects.

Some other student on the other side want to apply the Singleton to the Resource class.....

But If I think at the following execution on a possible client:

Resource r1 ResourcesManager = ResourcesManager.loadFromDb(1);
Resource r2 ResourcesManager = ResourcesManager.loadFromDb(2);

I will have the unexpected behaviour that r1=r2.... Because they refer to the same Resource Singleton  instance....

This is the solution I got in mind:

```<code>public class Resource {
     private String nome ;
     private int id;

     public Resource(){}

public void setName(String n){
    name=n;
}

public void setID(int n){
    id=n;
}

public String getName(){
     return name;
}

public int getID(){
     return id;
}

}

public class ResourcesManager {
  private static final ResourcesManager theInstance = new ResourcesManager();
  private ResourcesManager(){ }

  public static ResourcesManager getInstance(){
       return theInstance;
  }

  public static Resource loadFromDb(int resID) {
    Class.classForName(“Driver”);
    Connection con=DriverManager.get.Connection(...);
    Statement sta = con.createStatement();
    ResultSet rs = sta.executeQuery(“select name from resources where id=”+resID);
    if (rs.next){
              String name=rs.getString(“name”);
    }

    Resource s = new Resource();
    s.setName(name);
    s.setID(resID);
    return s;
  }
  public static Resource createNew(String name) {
    Resource s = new Resource();
    s.setName(name);
    return s;
  }
}
</code>```

答:
This is a design pattern academic question exercise

Okay, in that case I'll leave out the standard "you probably shouldn't use singleton" essay!

My solution would be to apply Singleton and Factory both to ResourcesManager class. Since I need only one instance of the Manager, and it's the one in charge of creating new objects.

Some other student on the other side want to apply the Singleton to the Resource class.....

Well, you're right and they're wrong. A resource isn't the slightest bit appropriate for a singleton for precisely the reasons you say. In fact you already seem to know most of this, but let's explicitly list all the reasons:

Conceptually, you can have multiple different resources in your system. Trying to model that concept with a class that you can only have one of makes no sense.

Practically, you probably do want to actually be able to have multiple different resources in memory at once. There's nothing in the code or question to suggest you can rule out such a fundamental scenario.

If you did do this, you'd get the bizarre behaviour that loading a new resource would change an existing one you had hold of in a very unexpected way. This would horribly couple together every piece of code that wanted to use a resource, likely leading to "big ball of mud" code, bugs, and lack of testibility.

Having to be able to update the Resource means you'd have to make its fields mutable, which is something you usually want to avoid.

The question is clearly hinting this to you by having a Resource returned from the ResourceManager. If its behaviour was to just keep updating the existing resource then it'd be void, and you'd access the single Resource as a singleton.

Likewise the naming of the methods in the question. createNew reads to me like whoever wrote the question screaming into the page "Don't use a singleton for this!" A method called create new shouldn't update existing

There's absolutely no reason to think the memory you'd potentially save from the singleton solution would be singificant.

In fact, expanding on that last point, its really unlikely that you're going to save memory from the singleton solution in the first place. There's three possible scenarios:

You only ever use a single resource. In this case, both solutions would have the same performance, they create one Resource object.

You need multiple different resources in memory at the same time (e.g. stored in a collection). In this case, your solution is the only one that works at all.

You use multiple different resources, but only one at a time. In this case, it's questionable whether you'd ever end up with multiple Resource objects in memory anyway. It's likely that by the time you're creating the second one, the first will have gone out of scope, and will have been garbage collected.

And in fact for there to be any possibility that the performance difference is significant, you'd have to use a large number of different resources, but only one at a time, but also do so in such a way that the ones you're done with don't go out of scope will you're loading the new ones. This is a really obscure and easily-fixed situation, and even then, it's likely that the memory overhead won't matter. So it's an extremely weak reason to base a decision on.

As a more general note, occasionally it happens that you need to go with a less expressive design which doesn't model the problem domain as well for performance reasons. If you think you're in this situation, there's two things you should do:

Resist it! It's actually pretty rare that this happens, so ask yourself is there a different, good design that meets your performance needs.

Isolate it. If you need to have something ugly somewhere, try to contain it so that only a small amount of other code needs to deal with that ugliness directly. A singleton is precisely the opposite- by definition it's system-wide! If you really found that creating multiple resources was a problem, it'd be a better, more isolated solution to change or add to your ResourcesManager's interface:

```<code>public class ResourcesManager {

     public static void populateFromDb(Resource resource, int resID) {
       ...
     }

     public static void reset(Resource resource, String name) {
        ...
     }
}
</code>```

Body:
This is a design pattern academic question exercise

Okay, in that case I'll leave out the standard "you probably shouldn't use singleton" essay!

My solution would be to apply Singleton and Factory both to ResourcesManager class. Since I need only one instance of the Manager, and it's the one in charge of creating new objects.

Some other student on the other side want to apply the Singleton to the Resource class.....

Well, you're right and they're wrong. A resource isn't the slightest bit appropriate for a singleton for precisely the reasons you say. In fact you already seem to know most of this, but let's explicitly list all the reasons:

Conceptually, you can have multiple different resources in your system. Trying to model that concept with a class that you can only have one of makes no sense.

Practically, you probably do want to actually be able to have multiple different resources in memory at once. There's nothing in the code or question to suggest you can rule out such a fundamental scenario.

If you did do this, you'd get the bizarre behaviour that loading a new resource would change an existing one you had hold of in a very unexpected way. This would horribly couple together every piece of code that wanted to use a resource, likely leading to "big ball of mud" code, bugs, and lack of testibility.

Having to be able to update the Resource means you'd have to make its fields mutable, which is something you usually want to avoid.

The question is clearly hinting this to you by having a Resource returned from the ResourceManager. If its behaviour was to just keep updating the existing resource then it'd be void, and you'd access the single Resource as a singleton.

Likewise the naming of the methods in the question. createNew reads to me like whoever wrote the question screaming into the page "Don't use a singleton for this!" A method called create new shouldn't update existing

There's absolutely no reason to think the memory you'd potentially save from the singleton solution would be singificant.

In fact, expanding on that last point, its really unlikely that you're going to save memory from the singleton solution in the first place. There's three possible scenarios:

You only ever use a single resource. In this case, both solutions would have the same performance, they create one Resource object.

You need multiple different resources in memory at the same time (e.g. stored in a collection). In this case, your solution is the only one that works at all.

You use multiple different resources, but only one at a time. In this case, it's questionable whether you'd ever end up with multiple Resource objects in memory anyway. It's likely that by the time you're creating the second one, the first will have gone out of scope, and will have been garbage collected.

And in fact for there to be any possibility that the performance difference is significant, you'd have to use a large number of different resources, but only one at a time, but also do so in such a way that the ones you're done with don't go out of scope will you're loading the new ones. This is a really obscure and easily-fixed situation, and even then, it's likely that the memory overhead won't matter. So it's an extremely weak reason to base a decision on.

As a more general note, occasionally it happens that you need to go with a less expressive design which doesn't model the problem domain as well for performance reasons. If you think you're in this situation, there's two things you should do:

Resist it! It's actually pretty rare that this happens, so ask yourself is there a different, good design that meets your performance needs.

Isolate it. If you need to have something ugly somewhere, try to contain it so that only a small amount of other code needs to deal with that ugliness directly. A singleton is precisely the opposite- by definition it's system-wide! If you really found that creating multiple resources was a problem, it'd be a better, more isolated solution to change or add to your ResourcesManager's interface:

public class ResourcesManager {

     public static void populateFromDb(Resource resource, int resID) {
       ...
     }

     public static void reset(Resource resource, String name) {
        ...
     }
}


问:
Should I write automated unit tests that fail when the code changes?
Generally when writing automated unit tests (eg JUnit, Karma)  I aim to:

cover all the boundary conditions

get a high level of coverage.

I heard someone say:

coverage and boundary conditions aren't enough for a unit test, you need to write them so they will break if the code changes.

This sounds good to me in theory - but I'm not sure how to apply it.

My question is: Should I write automated unit tests that fail when the code changes? If so, how.

答:
Your aim should be not to write unit tests that fail when the code changes, but unit tests that fail when the behaviour changes. Here, behaviour means anything that an external caller of the method wants it to do, like returning the right response to a question or saving the right thing to a databse. How it achieves that is its own internal implementation, not its behaviour.

By testing behaviour rather than implementation, you can refactor code to make improvements, and instantly verify whether you've accidentally changed the way it behaves by running your tests.

In reality, it's not possible to perfectly achieve this goal. If you have a method:

```<code>int add(int x, int y) {
    return x + y;
}
</code>```

You can write as many unit tests as you want for it, but it's extremely unlikely any of them will fail if you modify it to:

```<code>int add(int x, int y) {
    if(x==10731 && y == -405571) {
        return 0;
    }
    return x + y;
}
</code>```

However, you can take some sensible steps to get as close to full behavioural coverage as is practical:

As you said, think about boundary conditions and corner cases. These are the places where you're most likely to see accidental behaviour change.

Think of line-by-line coverage as "necessary, but not sufficient". Imagine trying to be as lazy as you possibly can while still getting full line-by-line coverage, and you'll see how easy it is to write an inadequate set of tests that follow this

Think about the behaviour your method is supposed to provide, and the branches it can follow. Ideally you should test that for every route through its implementation, it provides all the behaviour that's expected.

When you've written a set of tests, ask "Are there any implementations of my method which are at least as simple as the existing one, which would pass all of my tests, but which have the wrong behaviour?" This is a good rule of thumb to see if your behavioural coverage is good enough.

As shown in the example add method above, you can never really fully defend against modifications to your method that add extra stuff to it, making it less simple. But much more likely, bugs are going to sneak in through modifying or removing parts, without adding to the complexity (because, why would you refactor something in a way that adds to its complexity?). So by adding the "at least as simple" condition, you get something practically achievable.

Body:
Your aim should be not to write unit tests that fail when the code changes, but unit tests that fail when the behaviour changes. Here, behaviour means anything that an external caller of the method wants it to do, like returning the right response to a question or saving the right thing to a databse. How it achieves that is its own internal implementation, not its behaviour.

By testing behaviour rather than implementation, you can refactor code to make improvements, and instantly verify whether you've accidentally changed the way it behaves by running your tests.

In reality, it's not possible to perfectly achieve this goal. If you have a method:

int add(int x, int y) {
    return x + y;
}

You can write as many unit tests as you want for it, but it's extremely unlikely any of them will fail if you modify it to:

int add(int x, int y) {
    if(x==10731 && y == -405571) {
        return 0;
    }
    return x + y;
}

However, you can take some sensible steps to get as close to full behavioural coverage as is practical:

As you said, think about boundary conditions and corner cases. These are the places where you're most likely to see accidental behaviour change.

Think of line-by-line coverage as "necessary, but not sufficient". Imagine trying to be as lazy as you possibly can while still getting full line-by-line coverage, and you'll see how easy it is to write an inadequate set of tests that follow this

Think about the behaviour your method is supposed to provide, and the branches it can follow. Ideally you should test that for every route through its implementation, it provides all the behaviour that's expected.

When you've written a set of tests, ask "Are there any implementations of my method which are at least as simple as the existing one, which would pass all of my tests, but which have the wrong behaviour?" This is a good rule of thumb to see if your behavioural coverage is good enough.

As shown in the example add method above, you can never really fully defend against modifications to your method that add extra stuff to it, making it less simple. But much more likely, bugs are going to sneak in through modifying or removing parts, without adding to the complexity (because, why would you refactor something in a way that adds to its complexity?). So by adding the "at least as simple" condition, you get something practically achievable.


问:
Does pure functional programming become agility impediment?
While I like benefits of strong typing system, there is one thing that worries me the most. I think of strong type system as means of forcing design choices. If a team builds a system unaware of its poor design, types will only reflect bad design choices.

For example, a programmer often finds he needs to access (hopefully only read) a variable not declared in type of the function. This function may be used in many places where this constraint was implicit and fine. A signature change triggers a snowball effect. Change escalates until it hits main method or close to it. On top of that, we create a problem of compatibility and even merging becomes an issue.

In an impure (or object oriented) context, programmer will find his way to capture this variable. And I agree none of them will be pretty. But he can make progress without breaking stuff (most of the time). It's a form of technical debt, but people will (often rightfully) argue that it's beneficial to the project.

Has anyone experienced this kind of issues?

答:
First of all, pure functional programming does not necessarily require static typing. Pure programs can be written quite easily in Lisp, and there are other languages that are specifically designed to support pure dynamic programming.

Also, the kind of hack you're talking about in order to gain access to resources that are not explicit in the type of an object are the kind of techniques that lead to programs that are impossible to change because they are too highly coupled. They are the very antithesis of agility. To keep software agile, one needs explicit dependencies so that changes can be made safely.

A common approach is the use of dependency injection, a technique which can be performed with functional languages as well as object oriented.


问:
Does this class design violate the single responsibility principle?
Today I had an argument with someone.

I was explaining the benefits of having a rich domain model as opposed to an anemic domain model. And I demoed my point with a simple class looking like that:

```<code>public class Employee
{
    public Employee(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastname;
    }

    public string FirstName { get private set; }
    public string LastName { get; private set;}
    public int CountPaidDaysOffGranted { get; private set;}

    public void AddPaidDaysOffGranted(int numberOfdays)
    {
        // Do stuff
    }
}
</code>```

As he defended his anemic model approach, one of his arguments was: "I am a believer in SOLID. You are violating the single responsibility principle (SRP) as you are both representing data and performing logic in the same class."

I found this claim really surprising, as following this reasoning, any class having one property and one method violates the SRP, and therefore OOP in general is not SOLID, and functional programming is the only way to heaven.

I decided not to reply to his many arguments, but I am curious what the community thinks on this question.

If I had replied, I would have started by pointing to the paradox mentioned above, and then indicate that the SRP is highly dependent on the level of granularity you want to consider and that if you take it far enough, any class containing more than one property or one method violates it.

What would you have said?

Update: The example has been generously updated by guntbert to make the method more realistic and help us focus on the underlying discussion.

答:
The idea that mixing logic and data in any way is always wrong, is so ridiculous it doesn't even merit discussion. However, there is indeed a clear violation of single responsibility in the example, but it's not because there's a property DaysOfHolidays and a function AddHolidays(int).

It's because the employee's identity is intermingled with the holiday management, which is bad. The employee's identity is a complex thing that's required to track vacation, salary, overtime, to represent who's on which team, to link to performance reports, etc. An employee can also change first name, last name, or both, and stay the same employee. Employees may even have multiple spellings of their name such as an ASCII and a unicode spelling. People can have 0 to n first and/or last names. They may have different names in different jurisdictions. Tracking the identity of an employee is enough of a responsibility that holiday or vacation management cannot be added on top without calling it a second responsibility.


问:
Is it odd if Nodes in a LinkedList held references to the List object?
Its like, I want to call .moveToBefore(Node) on a Node object and have the node relocate to before the node passed in.

The problem arises if the node passed in is the head node. The List object will still reffer to the old head where as the old head will actually follow the new head further down the chain.

I guess this could be solved easily if the nodes held a reference to the List object. So want to know if there are any disadvantages if the node objects in a Linked List implementation held a reference to its parent "List" object.

答:
The potential problem with the Node class knowing about and using the List class directly is that you create a circular dependency between Node and List.

Circular dependencies can be acceptable if they're carefully contained to ensure you don't accidentally end up making all your classes circularly dependent on each other. This particular example is probably very easy to contain and unlikely to "infect" the rest of your classes, so I wouldn't rule it out as a potentially valid design. But it could still cause problems when maintaining the List itself, since in principle it means you can never change anything on List without checking that you aren't breaking Node in the process. For instance, what if you want to implement the splice operation for your List class? If your Nodes all contain references to the List they're in, then you have to update all of these references, which means slightly more complicated code, and the splice would end up taking O(n) instead of O(1) time. And, if you weren't consciously aware of the circular dependency, you might not have even realized you had to update those references (just imagine the kinds of bugs that would lead to).

For that reason, I would default to List.moveBefore(node1, node2) unless I had some compelling reason to put that method on the Node class instead. But if you do have such a reason, it's okay as long as you keep in mind that you can no longer make any changes to List or Node without checking both class' implementations for things that might break.


问:
Should you charge clients hours spent on the wrong track?
I took up a small CSS challenge to solve for a client and I'm going to be paid on a hourly rate.
I eventually solved it, it took 5 hours but I spent roughly 25% of the time in the wrong track, trying a CSS3 solution that only worked in recent browsers and finally discovering that no fallback is possible via JS (like I originally thought). Should I charge the client that 25%?

More details:
I didn't provide an estimate, I liked the challenge per se, so I started working on it before giving an estimate (but I have worked with him before, so I know he's not one of those people that have unrealistic expectations). At the very worst I will have spent 5 unpaid hours on an intriguing CSS challenge. And I will give the fairest possible estimate for both of us, since I will have already done the work. :)

Edit:
Thank you all, I wish I could accept more than one answer! I ended up not billing him for the extra hours (I billed him for 3 and a half), but I mentioned them, so that he knows I worked more on it than I billed him for. Maybe that's why he immediately accepted the "estimate" (which in that case wasn't an estimate, hence the quotes).

答:
If you hire a lawyer to argue a case for you, and they botch it and lose for you, you still pay their bill.

That's how all other professions do. There is no reason why programmers should do otherwise.

If the client thinks they paid too much, they won't come back to you. Keeping them as a repeat customer is the only sane reason for not billing for all hours worked.

ContentLicense: CC BY-SA 2.5


答:
The obvious disadvantage would be that a node cannot belong to more than one list. The fact that nodes can belong to more than one list is typically critical in the implementation of persistent data structures through structural sharing.

So, by making the node know about the list, you pretty much preclude making the list persistent, at least using the currently known techniques for doing so. Whether or not that is a restriction you can live with, depends on your requirements.

Title: Is it odd if Nodes in a LinkedList held references to the List object?

Body:
Its like, I want to call .moveToBefore(Node) on a Node object and have the node relocate to before the node passed in.

The problem arises if the node passed in is the head node. The List object will still reffer to the old head where as the old head will actually follow the new head further down the chain.

I guess this could be solved easily if the nodes held a reference to the List object. So want to know if there are any disadvantages if the node objects in a Linked List implementation held a reference to its parent "List" object.


问:
Is it reasonable to build applications (not games) using a component-entity-system architecture?
I know that when building applications (native or web) such as those in the Apple AppStore or Google Play app store that it's very common to use a Model-View-Controller architecture.

However, is it reasonable to also create applications using the Component-Entity-System architecture common in game engines?

答:
However, is it reasonable to also create applications using the
  Component-Entity-System architecture common in game engines?

To me, absolutely. I work in visual FX and studied a wide variety of systems in this field, their architectures (including CAD/CAM), hungry for SDKs and any papers that would give me a sense of the pros and cons of the seemingly-infinite architectural decisions that could be made, with even the most subtle ones not always making a subtle impact.

VFX is rather similar to games in that there is one central concept of a "scene", with viewports that display the rendered results. There also tends to be a lot of central loopy processing going on revolving around this scene constantly in animation contexts, where there might be physics happening, particle emitters spawning particles, meshes being animated and rendered, motion animations, etc, and ultimately to render them all to the user at the end.

Another similar concept to at least very complex game engines was the need for a "designer" aspect where designers could flexibly design scenes, including the ability do some lightweight programming of their own (scripts and nodes).

I found, over the years, that ECS made the best fit. Of course that's never completely divorced from subjectivity, but I would say it strongly appeared to give the fewest problems. It solved a lot more major problems we were always struggling with, while only giving us some few new minor ones in return.

Traditional OOP

More traditional OOP approaches can be really strong when you have a firm grasp of the design requirements upfront but not the implementation requirements. Whether through a flatter multiple interface approach or a more nested hierarchical ABC approach, it tends to cement the design and make it more difficult to change while making the implementation easier and safer to change. There's always a need for instability in any product that goes past a single version, so OOP approaches tend to skew stability (difficulty of change and lack of reasons for change) towards the design level, and instability (ease of change and reasons for change) to the implementation level.

However, against evolving user-end requirements, both design and implementation may need to frequently change. You might find something weird like a strong user-end need for the analogical creature that needs to be both plant and animal at the same time, completely invalidating the entire conceptual model you built. Normal object-oriented approaches don't protect you here, and can sometimes make such unanticipated, concept-breaking changes even harder. When very performance-critical areas are involved, the reasons for design changes further multiply.

Combining multiple, granular interfaces to form the conforming interface of an object can help a lot in stabilizing client code, but it doesn't help in stabilizing the subtypes which could sometimes dwarf the number of client dependencies. You can have one interface being used by only part of your system, for example, but with a thousand different subtypes implementing that interface. In that case, maintaining the complex subtypes (complex because they have so many disparate interface responsibilities to fulfill) can become the nightmare rather than the code using them through an interface. OOP tends to transfer complexity to the object level, while ECS transfers it to the client ("systems") level, and that can be ideal when there are very few systems but a whole bunch of conforming "objects" ("entities").

A class also owns its data privately, and thus can maintain invariants all on its own. Nevertheless, there are "coarse" invariants that can actually still be hard to maintain when objects interact with each other. For a complex system as a whole to be in a valid state often needs to consider a complex graph of objects, even if their individual invariants are properly maintained. Traditional OOP-style approaches can help with maintaining granular invariants, but can actually make it difficult to maintain broad, coarse invariants if the objects focus on teeny facets of the system.

That's where these kinds of lego-block-building ECS approaches or variants can be so helpful. Also with systems being coarser in design than the usual object, it becomes easier to maintain those kinds of coarse invariants at the bird's-eye view of the system. A lot of teeny object interactions turn into one big system focusing on one broad task instead of teeny little objects focusing on teeny little tasks with a dependency graph that would cover a kilometer of paper.

Yet I had to look outside of my field, at the gaming industry, to learn about ECS, though I was always one of a data-oriented mindset. Also, funnily enough, I almost made my way towards ECS on my own just iterating through and trying to come up with better designs. I didn't make it all the way though and missed a very crucial detail, which is the formalization of the "systems" part, and squashing components all the way down to raw data.

I'll try to go through how I ended up settling on ECS, and how it ended up solving all the problems with previous design iterations. I think that'll help to highlight exactly why the answer here could be a very strong "yes", that ECS is potentially applicable far beyond the gaming industry.

1980s Brute Force Architecture

The first architecture I worked on in the VFX industry had a long legacy that was already going past a decade since I joined the company. It was brute force crude C coding all the way (not a slant on C, as I love C, but the way it was being used here was really crude). A miniature and oversimplistic slice resembled dependencies like this:

And this is an enormously simplified diagram of one tiny piece of the system. Each of these clients in the diagram ("Rendering", "Physics", "Motion") would get some "generic" object through which they would check a type field, like so:

```<code>void transform(struct Object* obj, const float mat[16])
{
    switch (obj->type)
    {
        case camera:
            // cast to camera and do something with camera fields
            break;
        case light:
            // cast to light and do something with light fields
            break;
        ...
    }
}
</code>```

Of course with significantly uglier and more complex code than this. Often additional functions would be called from these switch cases which would recursively do the switch again and again and again. This diagram and code might almost look like ECS-lite, but there was no strong entity-component distinction ("is this object a camera?", not "does this object provide motion?"), and no formalization of "system" (just a bunch of nested functions going all over the place and mixing up responsibilities). In that case, just about everything was complicated, any function was a potential for a disaster waiting to happen.

Our testing procedure here often had to check things like meshes separate from other types of items, even if the identical thing was happening to both, since the brute force nature of the coding here (often accompanied by a lot of copy and paste) often made it very probable that what is otherwise the exact same logic could fail from one item type to the next. Trying to extend the system to handle new types of items was pretty hopeless, even though there was a strongly-expressed user-end need, as it was too difficult when we were struggling so much just to handle the existing types of items.

Some pros:

Uhh... doesn't take any engineering experience, I guess? This system does not require any knowledge of even basic concepts like polymorphism, it's totally brute force, so I guess even a beginner might be able to understand some of the code even if a pro at debugging can barely maintain it.

Some cons:

Maintenance nightmare. Our marketing team actually felt the need to boast the we fixed over 2000 unique bugs in one 3-year cycle. To me that's something to be embarrassed about that we had so many bugs in the first place, and that process probably still only fixed around 10% of the bugs total which were growing in number all the time.

About the most inflexible solution possible.

1990s COM Architecture

Most of the VFX industry uses this style of architecture from what I've gathered, reading documents about their design decisions and glancing at their software development kits.

It may not exactly be COM at the ABI level (some of these architectures could only have plugins written using the same compiler), but shares a lot of similar characteristics with interface queries made on objects to see what interfaces their components support.

With this kind of approach, the analogical transform function above came to resemble this form:

```<code>void transform(Object obj, const Matrix& mat)
{
    // Wrapper that performs an interface query to see if the
    // object implements the IMotion interface.
    MotionRef motion(obj);

    // If the object supported the IMotion interface:
    if (motion.valid())
    {
        // Transform the item through the IMotion interface.
        motion->transform(mat);
        ...
    }
}
</code>```

This is the approach the new team of that old codebase settled on, to eventually refactor towards. And it was a dramatic improvement over the original in terms of flexibility and maintainability, but there were still some issues I'll cover in the next section.

Some pros:

Dramatically more flexible/extensible/maintainable than the previous brute force solution.

Promotes a strong conformance to many principles of SOLID by making every interface completely abstract (stateless, no implementation, only pure interfaces).

Some cons:

Lots of boilerplate. Our components had to be published through a  registry in order to instantiate objects, the interfaces they supported required both inheriting ("implementing" in Java) the interface and providing some code to indicate which interfaces were available in a query.

Promoted duplicated logic all over the place as a result of the pure interfaces. For example, all components that implemented IMotion would always have the exact same state and exact same implementation for all of the functions. To mitigate this, we'd start centralizing base classes and helper functionality throughout the system for the things that would tend to be redundantly implemented the same way for the same interface, and possibly with multiple inheritance going on behind the hood, but it was pretty messy under the hood even though the client code had it easy.

Inefficiency: vtune sessions often showed the basic QueryInterface function almost always showing up as a middle to upper hotspot, and occasionally even the #1 hotspot. To mitigate that, we'd do things like have rendering parts of the codebase cache a list of objects already known to support IRenderable, but that significantly escalated the complexity and maintenance costs. Likewise, this was more difficult to measure but we noticed some definite slowdowns compared to the C-style coding we were doing before when every single interface required a dynamic dispatch. Things like branch mispredictions and optimization barriers are difficult to measure outside a little facet of code, but the users were just generally noticing the responsiveness of the user interface and things like that getting worse by comparing previous and newer versions of the software side-by-side for areas where the algorithmic complexity didn't change, only the constants.

Was still difficult to reason about correctness at a broader system level. Even though it was significantly easier than the previous approach, it was still hard to grasp the complex interactions between objects throughout this system, especially with some of the optimizations that started to become necessary against it.

We had trouble getting our interfaces correct. Even though there might only be one broad place in the system that uses an interface, user-end requirements would change over versions, and we would end up having to do cascading changes to all classes that implement the interface to accommodate a new function added to the interface, e.g., unless there was some abstract base class that was already centralizing the logic under the hood (some of these would manifest in the middle of these cascading changes in hopes of not repeatedly doing this again and again).

Pragmatic Response: Composition

One of the the things we were noticing before (or at least I was) that was causing issues was that IMotion might be implemented by 100 different classes but with the exact same implementation and state associated. Furthermore, it would only be used by a handful of systems like rendering, keyframed motion, and physics.

So in such a case, we might have like a 3-to-1 relationship between the systems using the interface to the interface, and a 100-to-1 relationship between the subtypes implementing the interface to the interface.

The complexity and maintenance then would be drastically skewed to the implementation and maintenance of 100 subtypes, instead of 3 client systems who depend on IMotion. This shifted all of our maintenance difficulties to the maintenance of these 100 subtypes, not the 3 places using the interface. Updating 3 places in the code with few or no "indirect efferent couplings" (as in dependencies to it but indirectly through an interface, not a direct dependency), no big deal: updating 100 subtype places with a boatload of "indirect efferent couplings", pretty big deal *.

* I realize it's odd and wrong to screw with the definition of
  "efferent couplings" in this sense from an implementation perspective, I
  just haven't found a better way to describe the maintenance complexity associated when both interface and corresponding implementations of a hundred subtypes must change.

So I had to push hard but I proposed that we try to become a little more pragmatic and relax the whole "pure interface" idea. It made no sense to me to make something like IMotion completely abstract and stateless unless we saw a benefit to it having a rich variety of implementations. In our case, for IMotion to have a rich variety of implementations would actually turn into quite a maintenance nightmare, as we didn't want variety. Instead we were iterating towards trying to make a single motion implementation that's really good against changing client requirements, and often were working around the pure interface idea a lot trying to force every implementor of IMotion to use the same implementation and state associated so that we don't duplicate goals.

Interfaces thus became more like broad Behaviors associated with an entity. IMotion would simply become a Motion "component" (I changed the way we defined "component" away from COM to one where is closer to the usual definition, of a piece making up a "complete" entity).

Instead of this:

```<code>class IMotion
{
public:
    virtual ~IMotion() {}
    virtual void transform(const Matrix& mat) = 0;
    ...
};
</code>```

We evolved it to something more like this:

```<code>class Motion
{
public:
    void transform(const Matrix& mat)
    {
        ...
    }
    ...

private:
    Matrix transformation;
    ...
};
</code>```

This is a blatant violation of the dependency inversion principle to start shifting away from the abstract back to the concrete, but to me such a level of abstraction is only useful if we can foresee a genuine need in some future, beyond a reasonable doubt and not exercising ridiculous "what if" scenarios completely detached from user experience (which would probably require a design change anyway), for such flexibility.

So we started evolving to this design. QueryInterface became more like QueryBehavior. Furthermore, it started to seem pointless to use inheritance here. We used composition instead. Objects turned into a collection of components whose availability could be queried and injected at runtime.

Some pros:

Was a lot easier to maintain still in our case than the previous, pure-interface COM-style system. Unforeseen surprises like a change in requirements or workflow complaints could be accommodated more easily with one very central and obvious Motion implementation, e.g., and not dispersed across a hundred subtypes.

Gave a whole new level of flexibility of the kind we actually needed. In our previous system, since inheritance models a static relationship, we could only effectively define new entities at compile-time in C++. We couldn't do it from the scripting language, e.g. With the composition approach, we could string together new entities on the fly at runtime by merely attaching components to them and adding them to a list. An "entity" turned into a blank canvas upon which we could just throw together a collage of whatever we needed on the fly, with relevant systems automatically recognizing and processing these entities as a result.

Some cons:

We were still having a hard time in the efficiency department, and maintainability in the performance-critical areas. Each system would still end up wanting to cache components of entities that provided these behaviors to avoid looping through them all repeatedly and checking what was available. Each system demanding performance would do this ever so slightly-differently, and was prone to a different set of bugs in failing to update this cached list and possibly a data structure (if some form of search was involved like frustum culling or raytracing) on some obscure scene change event, e.g.

There was still something awkward and complex that I couldn't put my finger on related to all these granular little behavioral, simple objects. We still spawned a lot of events to deal with interactions between these "behavior" objects that were sometimes necessary, and the result was very decentralized code. Each little object was easy to test for correctness and, taken individually, often were perfectly correct. Yet it still felt like we were trying to maintain a massive ecosystem composed of little villages and trying to reason about what they all individually do and add up to make as a whole. The C-style 80s codebase felt like one epic, overpopulated megalopolis which was definitely a maintenance nightmare, but dispersing that complexity across into too many teeny villages was also making it hard to think about the system at a bird's-eye view without getting overwhelmed by the complexity of the interactions between everything.

Loss of flexibility with the lack of abstraction but in an area where we never actually encountered a genuine need for it, so hardly a practical con (though definitely at least a theoretical one).

Preserving ABI compatibility was always hard, and this made it harder by requiring stable data and not just stable interface associated with a "behavior". However, we could easily add new behaviors and simply deprecate existing ones if a state change was needed, and that was arguably easier than doing backflips underneath the interfaces at the subtype-level to handle versioning concerns.

One phenomena that occurred was that, since we lost the abstraction on these behavioral components, we had more of them. For example, instead of an abstract IRenderable component, we'd attach an object with a concrete Mesh or PointSprites component. The rendering system would know how to render Mesh and PointSprites components and would find entities that provide such components and draw those. At other times, we had miscellaneous renderables like SceneLabel that we discovered we needed in hindsight, and so we'd attach a SceneLabel in those cases to relevant entities (possibly in addition to a Mesh). The rendering system implement would then be updated to know how to render entities that provided those, and that was a pretty easy change to make.

In this case, an entity composed of components could also then be used as a component to another entity. We'd build things up that way by hooking up lego blocks.

ECS: Systems and Raw Data Components

That last system was as far as I made it on my own, and we were still bastardizing it with COM. It felt like it was wanting to become an entity-component system but I was not familiar with it at the time. I was looking around at COM-style examples which saturated my field, when I should have been looking at AAA game engines for architectural inspiration. I finally started doing that.

What I was missing were several key ideas:

The formalization of "systems" to process "components".

"Components" being raw data rather than behavioral objects composed together into a bigger object.

Entities as nothing more than a strict ID associated to a collection of components.

I finally left that company and started working on an ECS as an indy (still working on it while draining my savings), and it has been the easiest system to manage by far.

What I noticed with the ECS approach was that it solved the problems I was still struggling with above. Most importantly to me, it felt like we were managing healthy-sized "cities" instead of teeny little villages with complex interactions. It wasn't as hard to maintain as a monolithic "megalopolis", too big in its population to effectively manage, but wasn't as chaotic as a world filled with tiny little villages interacting with each other where just thinking about the trade routes in between them formed a nightmarish graph. ECS distilled all the complexity towards bulky "systems", like a rendering system, a healthy-sized "city" but not an "overpopulated megalopolis".

Components becoming raw data felt really weird to me at first, as it breaks even the basic information hiding principle of OOP. It was kind of challenging one of the biggest values I held dear about OOP, which was its ability to maintain invariants which required encapsulation and information hiding. But it started to become a non-concern as it quickly became obvious what was happening with just a dozen or so broad systems transforming that data instead of such logic being dispersed across hundreds to thousands of subtypes implementing a combo of interfaces. I tend to think of it like still in an OOP-style fashion except spread out where the systems are providing the functionality and implementation that access the data, the components are providing the data, and the entities are providing components.

It became even easier, counter-intuitively, to reason about the side effects caused by the system when there were just a handful of bulky systems transforming the data in broad passes. The system became a lot "flatter", my call stacks became shallower than ever before for each thread. I could think about the system at that overseer level and not run into weird surprises.

Likewise, it made even the performance-critical areas simple with respect to eliminating those queries. Since the idea of "System" became very formalized, a system could subscribe to the components it was interested in, and just be handed a cached list of entities which satisfy that criteria. Each individual one didn't have to manage that caching optimization, it became centralized to a single place.

Some pros:

Seems to just solve almost every major architectural problem I was encountering in my career without ever feeling trapped in a design corner when encountering unanticipated needs.

Some cons:

I still have a hard time wrapping my head around it sometimes, and it's not the most mature or well-established paradigm even within the game industry, where people argue about exactly what it means and how to do things. It's definitely not something I could have done with the former team I worked with, which consisted of members deeply hooked to the COM-style mindset or the 1980s C-style mindset of the original codebase. Where I get confused sometimes is like how to model graph-style relationships between components, but I've always found a solution which didn't turn out to be horrible later where I can just make a component dependent upon another one ("this motion component depends on this other one as a parent, and the system will use memoization to avoid repeatedly doing the same recursive motion calculations", e.g.)

ABI is still difficult, but so far I'd even venture to say that it's easier than pure interface approach. It's a shift in mindset: data stability becomes the sole focus for ABI, rather than interface stability, and in some ways it's easier to achieve data stability than interface stability (ex: no temptations to change a function just because it needs a new parameter. That kind of stuff happens inside coarse system implementations which don't break ABI).

However, is it reasonable to also create applications using the
  Component-Entity-System architecture common in game engines?

So anyway, I'd say absolutely "yes", with my personal VFX example being a strong candidate. But that's still fairly similar to the needs of gaming.

I haven't put it to practice in more remote areas completely detached from the concerns of game engines (VFX is quite similar), but it seems to me like far more areas are good candidates for an ECS approach. Maybe even a GUI system would be suitable for one, but I still use a more OOP approach there (but without deep inheritance unlike Qt, e.g.).

It's widely-unexplored territory, but it seems suitable to me whenever your entities can be composed of a rich combination of "traits" (and exactly what combo of traits they provide being ever subject to change), and where you have a handful of generalized systems that process entities that have the necessary traits.

It becomes a very practical alternative in those cases to any scenario where you might be tempted to use something like multiple inheritance or an emulation of the concept (mixins, e.g.) only to produce hundreds or more combos in a deep inheritance hierarchy or hundreds of combos of classes in a flat hierarchy implementing a specific combo of interfaces, but where your systems are few in number (dozens, e.g.).

In those cases, the complexity of the codebase starts to feels more proportional to the number of systems instead of the number of type combinations, since each type is now just an entity composing components which are nothing more than raw data. GUI systems naturally fit these kinds of specs where they might have hundreds of possible widget types combined from other base types or interfaces, but only a handful of systems to process them (layout system, rendering system, etc). If a GUI system used ECS, it would probably be so much easier to reason about the correctness of the system when all the functionality is provided by a handful of these systems instead of hundreds of different object types with inherited interfaces or base classes. If a GUI system used ECS, widgets would have no functionality, only data. Only the handful of systems that process widget entities would have functionality. How overridable events for a widget would be handled is beyond me, but just based on my limited experience so far, I haven't found a case where that type of logic could not be transferred centrally to a given system in a way that, in hindsight, yielded a much more elegant solution that I'd ever expect.

I would love to see it employed in more fields, as it was a lifesaver in mine. Of course it's ill-suited if your design doesn't break down this way, from entities aggregating components to coarse systems that process those components, but if they naturally fit this kind of model, it is the most wonderful thing I've encountered yet.

Body:
However, is it reasonable to also create applications using the
  Component-Entity-System architecture common in game engines?

To me, absolutely. I work in visual FX and studied a wide variety of systems in this field, their architectures (including CAD/CAM), hungry for SDKs and any papers that would give me a sense of the pros and cons of the seemingly-infinite architectural decisions that could be made, with even the most subtle ones not always making a subtle impact.

VFX is rather similar to games in that there is one central concept of a "scene", with viewports that display the rendered results. There also tends to be a lot of central loopy processing going on revolving around this scene constantly in animation contexts, where there might be physics happening, particle emitters spawning particles, meshes being animated and rendered, motion animations, etc, and ultimately to render them all to the user at the end.

Another similar concept to at least very complex game engines was the need for a "designer" aspect where designers could flexibly design scenes, including the ability do some lightweight programming of their own (scripts and nodes).

I found, over the years, that ECS made the best fit. Of course that's never completely divorced from subjectivity, but I would say it strongly appeared to give the fewest problems. It solved a lot more major problems we were always struggling with, while only giving us some few new minor ones in return.

Traditional OOP

More traditional OOP approaches can be really strong when you have a firm grasp of the design requirements upfront but not the implementation requirements. Whether through a flatter multiple interface approach or a more nested hierarchical ABC approach, it tends to cement the design and make it more difficult to change while making the implementation easier and safer to change. There's always a need for instability in any product that goes past a single version, so OOP approaches tend to skew stability (difficulty of change and lack of reasons for change) towards the design level, and instability (ease of change and reasons for change) to the implementation level.

However, against evolving user-end requirements, both design and implementation may need to frequently change. You might find something weird like a strong user-end need for the analogical creature that needs to be both plant and animal at the same time, completely invalidating the entire conceptual model you built. Normal object-oriented approaches don't protect you here, and can sometimes make such unanticipated, concept-breaking changes even harder. When very performance-critical areas are involved, the reasons for design changes further multiply.

Combining multiple, granular interfaces to form the conforming interface of an object can help a lot in stabilizing client code, but it doesn't help in stabilizing the subtypes which could sometimes dwarf the number of client dependencies. You can have one interface being used by only part of your system, for example, but with a thousand different subtypes implementing that interface. In that case, maintaining the complex subtypes (complex because they have so many disparate interface responsibilities to fulfill) can become the nightmare rather than the code using them through an interface. OOP tends to transfer complexity to the object level, while ECS transfers it to the client ("systems") level, and that can be ideal when there are very few systems but a whole bunch of conforming "objects" ("entities").

A class also owns its data privately, and thus can maintain invariants all on its own. Nevertheless, there are "coarse" invariants that can actually still be hard to maintain when objects interact with each other. For a complex system as a whole to be in a valid state often needs to consider a complex graph of objects, even if their individual invariants are properly maintained. Traditional OOP-style approaches can help with maintaining granular invariants, but can actually make it difficult to maintain broad, coarse invariants if the objects focus on teeny facets of the system.

That's where these kinds of lego-block-building ECS approaches or variants can be so helpful. Also with systems being coarser in design than the usual object, it becomes easier to maintain those kinds of coarse invariants at the bird's-eye view of the system. A lot of teeny object interactions turn into one big system focusing on one broad task instead of teeny little objects focusing on teeny little tasks with a dependency graph that would cover a kilometer of paper.

Yet I had to look outside of my field, at the gaming industry, to learn about ECS, though I was always one of a data-oriented mindset. Also, funnily enough, I almost made my way towards ECS on my own just iterating through and trying to come up with better designs. I didn't make it all the way though and missed a very crucial detail, which is the formalization of the "systems" part, and squashing components all the way down to raw data.

I'll try to go through how I ended up settling on ECS, and how it ended up solving all the problems with previous design iterations. I think that'll help to highlight exactly why the answer here could be a very strong "yes", that ECS is potentially applicable far beyond the gaming industry.

1980s Brute Force Architecture

The first architecture I worked on in the VFX industry had a long legacy that was already going past a decade since I joined the company. It was brute force crude C coding all the way (not a slant on C, as I love C, but the way it was being used here was really crude). A miniature and oversimplistic slice resembled dependencies like this:

And this is an enormously simplified diagram of one tiny piece of the system. Each of these clients in the diagram ("Rendering", "Physics", "Motion") would get some "generic" object through which they would check a type field, like so:

void transform(struct Object* obj, const float mat[16])
{
    switch (obj->type)
    {
        case camera:
            // cast to camera and do something with camera fields
            break;
        case light:
            // cast to light and do something with light fields
            break;
        ...
    }
}

Of course with significantly uglier and more complex code than this. Often additional functions would be called from these switch cases which would recursively do the switch again and again and again. This diagram and code might almost look like ECS-lite, but there was no strong entity-component distinction ("is this object a camera?", not "does this object provide motion?"), and no formalization of "system" (just a bunch of nested functions going all over the place and mixing up responsibilities). In that case, just about everything was complicated, any function was a potential for a disaster waiting to happen.

Our testing procedure here often had to check things like meshes separate from other types of items, even if the identical thing was happening to both, since the brute force nature of the coding here (often accompanied by a lot of copy and paste) often made it very probable that what is otherwise the exact same logic could fail from one item type to the next. Trying to extend the system to handle new types of items was pretty hopeless, even though there was a strongly-expressed user-end need, as it was too difficult when we were struggling so much just to handle the existing types of items.

Some pros:

Uhh... doesn't take any engineering experience, I guess? This system does not require any knowledge of even basic concepts like polymorphism, it's totally brute force, so I guess even a beginner might be able to understand some of the code even if a pro at debugging can barely maintain it.

Some cons:

Maintenance nightmare. Our marketing team actually felt the need to boast the we fixed over 2000 unique bugs in one 3-year cycle. To me that's something to be embarrassed about that we had so many bugs in the first place, and that process probably still only fixed around 10% of the bugs total which were growing in number all the time.

About the most inflexible solution possible.

1990s COM Architecture

Most of the VFX industry uses this style of architecture from what I've gathered, reading documents about their design decisions and glancing at their software development kits.

It may not exactly be COM at the ABI level (some of these architectures could only have plugins written using the same compiler), but shares a lot of similar characteristics with interface queries made on objects to see what interfaces their components support.

With this kind of approach, the analogical transform function above came to resemble this form:

void transform(Object obj, const Matrix& mat)
{
    // Wrapper that performs an interface query to see if the
    // object implements the IMotion interface.
    MotionRef motion(obj);

    // If the object supported the IMotion interface:
    if (motion.valid())
    {
        // Transform the item through the IMotion interface.
        motion->transform(mat);
        ...
    }
}

This is the approach the new team of that old codebase settled on, to eventually refactor towards. And it was a dramatic improvement over the original in terms of flexibility and maintainability, but there were still some issues I'll cover in the next section.

Some pros:

Dramatically more flexible/extensible/maintainable than the previous brute force solution.

Promotes a strong conformance to many principles of SOLID by making every interface completely abstract (stateless, no implementation, only pure interfaces).

Some cons:

Lots of boilerplate. Our components had to be published through a  registry in order to instantiate objects, the interfaces they supported required both inheriting ("implementing" in Java) the interface and providing some code to indicate which interfaces were available in a query.

Promoted duplicated logic all over the place as a result of the pure interfaces. For example, all components that implemented IMotion would always have the exact same state and exact same implementation for all of the functions. To mitigate this, we'd start centralizing base classes and helper functionality throughout the system for the things that would tend to be redundantly implemented the same way for the same interface, and possibly with multiple inheritance going on behind the hood, but it was pretty messy under the hood even though the client code had it easy.

Inefficiency: vtune sessions often showed the basic QueryInterface function almost always showing up as a middle to upper hotspot, and occasionally even the #1 hotspot. To mitigate that, we'd do things like have rendering parts of the codebase cache a list of objects already known to support IRenderable, but that significantly escalated the complexity and maintenance costs. Likewise, this was more difficult to measure but we noticed some definite slowdowns compared to the C-style coding we were doing before when every single interface required a dynamic dispatch. Things like branch mispredictions and optimization barriers are difficult to measure outside a little facet of code, but the users were just generally noticing the responsiveness of the user interface and things like that getting worse by comparing previous and newer versions of the software side-by-side for areas where the algorithmic complexity didn't change, only the constants.

Was still difficult to reason about correctness at a broader system level. Even though it was significantly easier than the previous approach, it was still hard to grasp the complex interactions between objects throughout this system, especially with some of the optimizations that started to become necessary against it.

We had trouble getting our interfaces correct. Even though there might only be one broad place in the system that uses an interface, user-end requirements would change over versions, and we would end up having to do cascading changes to all classes that implement the interface to accommodate a new function added to the interface, e.g., unless there was some abstract base class that was already centralizing the logic under the hood (some of these would manifest in the middle of these cascading changes in hopes of not repeatedly doing this again and again).

Pragmatic Response: Composition

One of the the things we were noticing before (or at least I was) that was causing issues was that IMotion might be implemented by 100 different classes but with the exact same implementation and state associated. Furthermore, it would only be used by a handful of systems like rendering, keyframed motion, and physics.

So in such a case, we might have like a 3-to-1 relationship between the systems using the interface to the interface, and a 100-to-1 relationship between the subtypes implementing the interface to the interface.

The complexity and maintenance then would be drastically skewed to the implementation and maintenance of 100 subtypes, instead of 3 client systems who depend on IMotion. This shifted all of our maintenance difficulties to the maintenance of these 100 subtypes, not the 3 places using the interface. Updating 3 places in the code with few or no "indirect efferent couplings" (as in dependencies to it but indirectly through an interface, not a direct dependency), no big deal: updating 100 subtype places with a boatload of "indirect efferent couplings", pretty big deal *.

* I realize it's odd and wrong to screw with the definition of
  "efferent couplings" in this sense from an implementation perspective, I
  just haven't found a better way to describe the maintenance complexity associated when both interface and corresponding implementations of a hundred subtypes must change.

So I had to push hard but I proposed that we try to become a little more pragmatic and relax the whole "pure interface" idea. It made no sense to me to make something like IMotion completely abstract and stateless unless we saw a benefit to it having a rich variety of implementations. In our case, for IMotion to have a rich variety of implementations would actually turn into quite a maintenance nightmare, as we didn't want variety. Instead we were iterating towards trying to make a single motion implementation that's really good against changing client requirements, and often were working around the pure interface idea a lot trying to force every implementor of IMotion to use the same implementation and state associated so that we don't duplicate goals.

Interfaces thus became more like broad Behaviors associated with an entity. IMotion would simply become a Motion "component" (I changed the way we defined "component" away from COM to one where is closer to the usual definition, of a piece making up a "complete" entity).

Instead of this:

class IMotion
{
public:
    virtual ~IMotion() {}
    virtual void transform(const Matrix& mat) = 0;
    ...
};

We evolved it to something more like this:

class Motion
{
public:
    void transform(const Matrix& mat)
    {
        ...
    }
    ...

private:
    Matrix transformation;
    ...
};

This is a blatant violation of the dependency inversion principle to start shifting away from the abstract back to the concrete, but to me such a level of abstraction is only useful if we can foresee a genuine need in some future, beyond a reasonable doubt and not exercising ridiculous "what if" scenarios completely detached from user experience (which would probably require a design change anyway), for such flexibility.

So we started evolving to this design. QueryInterface became more like QueryBehavior. Furthermore, it started to seem pointless to use inheritance here. We used composition instead. Objects turned into a collection of components whose availability could be queried and injected at runtime.

Some pros:

Was a lot easier to maintain still in our case than the previous, pure-interface COM-style system. Unforeseen surprises like a change in requirements or workflow complaints could be accommodated more easily with one very central and obvious Motion implementation, e.g., and not dispersed across a hundred subtypes.

Gave a whole new level of flexibility of the kind we actually needed. In our previous system, since inheritance models a static relationship, we could only effectively define new entities at compile-time in C++. We couldn't do it from the scripting language, e.g. With the composition approach, we could string together new entities on the fly at runtime by merely attaching components to them and adding them to a list. An "entity" turned into a blank canvas upon which we could just throw together a collage of whatever we needed on the fly, with relevant systems automatically recognizing and processing these entities as a result.

Some cons:

We were still having a hard time in the efficiency department, and maintainability in the performance-critical areas. Each system would still end up wanting to cache components of entities that provided these behaviors to avoid looping through them all repeatedly and checking what was available. Each system demanding performance would do this ever so slightly-differently, and was prone to a different set of bugs in failing to update this cached list and possibly a data structure (if some form of search was involved like frustum culling or raytracing) on some obscure scene change event, e.g.

There was still something awkward and complex that I couldn't put my finger on related to all these granular little behavioral, simple objects. We still spawned a lot of events to deal with interactions between these "behavior" objects that were sometimes necessary, and the result was very decentralized code. Each little object was easy to test for correctness and, taken individually, often were perfectly correct. Yet it still felt like we were trying to maintain a massive ecosystem composed of little villages and trying to reason about what they all individually do and add up to make as a whole. The C-style 80s codebase felt like one epic, overpopulated megalopolis which was definitely a maintenance nightmare, but dispersing that complexity across into too many teeny villages was also making it hard to think about the system at a bird's-eye view without getting overwhelmed by the complexity of the interactions between everything.

Loss of flexibility with the lack of abstraction but in an area where we never actually encountered a genuine need for it, so hardly a practical con (though definitely at least a theoretical one).

Preserving ABI compatibility was always hard, and this made it harder by requiring stable data and not just stable interface associated with a "behavior". However, we could easily add new behaviors and simply deprecate existing ones if a state change was needed, and that was arguably easier than doing backflips underneath the interfaces at the subtype-level to handle versioning concerns.

One phenomena that occurred was that, since we lost the abstraction on these behavioral components, we had more of them. For example, instead of an abstract IRenderable component, we'd attach an object with a concrete Mesh or PointSprites component. The rendering system would know how to render Mesh and PointSprites components and would find entities that provide such components and draw those. At other times, we had miscellaneous renderables like SceneLabel that we discovered we needed in hindsight, and so we'd attach a SceneLabel in those cases to relevant entities (possibly in addition to a Mesh). The rendering system implement would then be updated to know how to render entities that provided those, and that was a pretty easy change to make.

In this case, an entity composed of components could also then be used as a component to another entity. We'd build things up that way by hooking up lego blocks.

ECS: Systems and Raw Data Components

That last system was as far as I made it on my own, and we were still bastardizing it with COM. It felt like it was wanting to become an entity-component system but I was not familiar with it at the time. I was looking around at COM-style examples which saturated my field, when I should have been looking at AAA game engines for architectural inspiration. I finally started doing that.

What I was missing were several key ideas:

The formalization of "systems" to process "components".

"Components" being raw data rather than behavioral objects composed together into a bigger object.

Entities as nothing more than a strict ID associated to a collection of components.

I finally left that company and started working on an ECS as an indy (still working on it while draining my savings), and it has been the easiest system to manage by far.

What I noticed with the ECS approach was that it solved the problems I was still struggling with above. Most importantly to me, it felt like we were managing healthy-sized "cities" instead of teeny little villages with complex interactions. It wasn't as hard to maintain as a monolithic "megalopolis", too big in its population to effectively manage, but wasn't as chaotic as a world filled with tiny little villages interacting with each other where just thinking about the trade routes in between them formed a nightmarish graph. ECS distilled all the complexity towards bulky "systems", like a rendering system, a healthy-sized "city" but not an "overpopulated megalopolis".

Components becoming raw data felt really weird to me at first, as it breaks even the basic information hiding principle of OOP. It was kind of challenging one of the biggest values I held dear about OOP, which was its ability to maintain invariants which required encapsulation and information hiding. But it started to become a non-concern as it quickly became obvious what was happening with just a dozen or so broad systems transforming that data instead of such logic being dispersed across hundreds to thousands of subtypes implementing a combo of interfaces. I tend to think of it like still in an OOP-style fashion except spread out where the systems are providing the functionality and implementation that access the data, the components are providing the data, and the entities are providing components.

It became even easier, counter-intuitively, to reason about the side effects caused by the system when there were just a handful of bulky systems transforming the data in broad passes. The system became a lot "flatter", my call stacks became shallower than ever before for each thread. I could think about the system at that overseer level and not run into weird surprises.

Likewise, it made even the performance-critical areas simple with respect to eliminating those queries. Since the idea of "System" became very formalized, a system could subscribe to the components it was interested in, and just be handed a cached list of entities which satisfy that criteria. Each individual one didn't have to manage that caching optimization, it became centralized to a single place.

Some pros:

Seems to just solve almost every major architectural problem I was encountering in my career without ever feeling trapped in a design corner when encountering unanticipated needs.

Some cons:

I still have a hard time wrapping my head around it sometimes, and it's not the most mature or well-established paradigm even within the game industry, where people argue about exactly what it means and how to do things. It's definitely not something I could have done with the former team I worked with, which consisted of members deeply hooked to the COM-style mindset or the 1980s C-style mindset of the original codebase. Where I get confused sometimes is like how to model graph-style relationships between components, but I've always found a solution which didn't turn out to be horrible later where I can just make a component dependent upon another one ("this motion component depends on this other one as a parent, and the system will use memoization to avoid repeatedly doing the same recursive motion calculations", e.g.)

ABI is still difficult, but so far I'd even venture to say that it's easier than pure interface approach. It's a shift in mindset: data stability becomes the sole focus for ABI, rather than interface stability, and in some ways it's easier to achieve data stability than interface stability (ex: no temptations to change a function just because it needs a new parameter. That kind of stuff happens inside coarse system implementations which don't break ABI).

However, is it reasonable to also create applications using the
  Component-Entity-System architecture common in game engines?

So anyway, I'd say absolutely "yes", with my personal VFX example being a strong candidate. But that's still fairly similar to the needs of gaming.

I haven't put it to practice in more remote areas completely detached from the concerns of game engines (VFX is quite similar), but it seems to me like far more areas are good candidates for an ECS approach. Maybe even a GUI system would be suitable for one, but I still use a more OOP approach there (but without deep inheritance unlike Qt, e.g.).

It's widely-unexplored territory, but it seems suitable to me whenever your entities can be composed of a rich combination of "traits" (and exactly what combo of traits they provide being ever subject to change), and where you have a handful of generalized systems that process entities that have the necessary traits.

It becomes a very practical alternative in those cases to any scenario where you might be tempted to use something like multiple inheritance or an emulation of the concept (mixins, e.g.) only to produce hundreds or more combos in a deep inheritance hierarchy or hundreds of combos of classes in a flat hierarchy implementing a specific combo of interfaces, but where your systems are few in number (dozens, e.g.).

In those cases, the complexity of the codebase starts to feels more proportional to the number of systems instead of the number of type combinations, since each type is now just an entity composing components which are nothing more than raw data. GUI systems naturally fit these kinds of specs where they might have hundreds of possible widget types combined from other base types or interfaces, but only a handful of systems to process them (layout system, rendering system, etc). If a GUI system used ECS, it would probably be so much easier to reason about the correctness of the system when all the functionality is provided by a handful of these systems instead of hundreds of different object types with inherited interfaces or base classes. If a GUI system used ECS, widgets would have no functionality, only data. Only the handful of systems that process widget entities would have functionality. How overridable events for a widget would be handled is beyond me, but just based on my limited experience so far, I haven't found a case where that type of logic could not be transferred centrally to a given system in a way that, in hindsight, yielded a much more elegant solution that I'd ever expect.

I would love to see it employed in more fields, as it was a lifesaver in mine. Of course it's ill-suited if your design doesn't break down this way, from entities aggregating components to coarse systems that process those components, but if they naturally fit this kind of model, it is the most wonderful thing I've encountered yet.