Skip to main content
deleted 824 characters in body
Source Link
Christophe
  • 80.6k
  • 11
  • 132
  • 199

In short

In short

NoStrengthening the two clauses arepreconditions and weakening post conditions is not at all the same. They These conditions have a different nature regardless of how you enforce them. And

In many case, you could weaken preconditions or strengthen pre conditions independently, while both can be infringed separately. Numerous countermay result failed tests cases when reusing super-examples invalidate the claim about these beingtype tests for the same thingsub-type.

More details

On LSP, contracts and exceptions (the theory)More details

On LSP and contracts

LSP is about promises made in a contract and not about implementation and exceptions. The preconditions and post conditions do not even need to be implemented by the type and its subtype. Therefore, here a very important distinction:

  • preconditions is about conditions that need to be fulfilled before the an operation is performed. If you call the operation but these conditionswhen they are not met, it doesn't mean that you have to throw exceptions. It just means that the promises of that operation (invariants, post conditions) are notno longer guaranteed.
  • postconditions is about conditions thatwhat must be guaranteed after the operation is performed, provided the preconditions were initially met. If they are not fulfilled, there's something wrong in the code.

Contracts are not about exception (and vice-versa)

AddingThe pre-/post-conditions do not need to be implemented by the type and its subtype. Adding checks and exceptions inside(inside the type'sthe operations, or outside, in the code that uses the type, before/after calling them) is defensive programming. It helps sometimes to deduce the contract, but not always. For example, whenever there is a divide in some formula, you may find preconditions that must be fulfilled to avoid the divide by zero to happen, even if you don't explicitly throw.

By the way, depending how you design and document contracts, an exception could evenThrowing may be a promised behavior of the contract insteadsign of a failed pre/post-condition(). See forBut an exception could as well be a promised behavior of the contract. For example the following contract that would make your subtype LSP compliant without changing anything in the code:

Task::Close() ensures that the task is in a closed state if closing is possible or that an exception TaskCannotBeClosed is thrown.Task::Close() ensures that the task is in a closed state if closing is possible or that an exception TaskCannotBeClosed is thrown.

Hypothetical counter example (the practice)

But enough theory. NowIn this case, in your Task no exception is thrown not because of a counter-exampledifferent contract but because simpler tasks can alway be closed and hence, that uses exceptionsno need to throw. And throwing for pre and post conditions like you didProjectTask is no longer a strengthening of preconditions. Take

Example with independent pre and post conditions.

Take the following pseudo-code, which has more distinctunrelated pre and post conditions. Please ignore all the other design and style issues (I would of course design this very differently, it's just to illustrate the case):

Here aYou would have an LSP infringement that strengthensby strengthening the precondition but leaves post condition unchanged:if you would have a TransactionDangerousGoods that has a precondition that customer is 18+. Absolutely no impact on postcondition.

class TransactionDangerousGoods extends Transaction {
  Money sellProduct (Customer c, Product p, Quantity q) {
       
  if (c.age()<18) // precondition: Customer is aged 18+ 
    throw CustomerTooYoung;

  ... do something more specific 

  // keep same postcondition check as before
  }
  ...
} 

Here anotherYou would have an LSP infringement on post condition only and that completely misuses the design:

class Takeback  extends Transaction {
  Money sellProduct (Customer c, Product p, Quantity q) {
       
    // no precondition, weakens precondition, is ok.   

    ... do something to take the product and reimburse customer  

    // no postcondition, weakens postcondition ;
      return valueCashedIn ;  // negative as it is a reimbursement 
    }
  ...
} 

Hence-condition, we see that strengthening precondition and weakening post condition can in many cases be very differentif you would have a Takeback class as subtype of Transaction, if the tackeback would misuse the sellProduct() to return the reimbursed amount as negative value.

In short

No the two clauses are not at all the same. They have a different nature regardless of how you enforce them. And both can be infringed separately. Numerous counter-examples invalidate the claim about these being the same thing.

More details

On LSP, contracts and exceptions (the theory)

LSP is about promises made in a contract and not about implementation and exceptions. The preconditions and post conditions do not even need to be implemented by the type and its subtype. Therefore, here a very important distinction:

  • preconditions is about conditions that need to be fulfilled before the an operation is performed. If you call the operation but these conditions are not met, it doesn't mean that you have to throw exceptions. It just means that the promises of that operation (invariants, post conditions) are not guaranteed.
  • postconditions is about conditions that must be guaranteed after the operation is performed, provided the preconditions were initially met. If they are not fulfilled, there's something wrong in the code.

Adding checks and exceptions inside the type's operations, or outside, in the code that uses the type, is defensive programming. It helps sometimes to deduce the contract, but not always. For example, whenever there is a divide in some formula, you may find preconditions that must be fulfilled to avoid the divide by zero to happen, even if you don't explicitly throw.

By the way, depending how you design and document contracts, an exception could even be a promised behavior of the contract instead of a failed pre/post-condition(). See for example the following contract that would make your subtype LSP compliant:

Task::Close() ensures that the task is in a closed state if closing is possible or that an exception TaskCannotBeClosed is thrown.

Hypothetical counter example (the practice)

But enough theory. Now a counter-example, that uses exceptions for pre and post conditions like you did. Take the following pseudo-code, which has more distinct pre and post conditions. Please ignore all the other design and style issues (I would of course design this very differently, it's just to illustrate the case):

Here a LSP infringement that strengthens the precondition but leaves post condition unchanged:

class TransactionDangerousGoods extends Transaction {
  Money sellProduct (Customer c, Product p, Quantity q) {
       
  if (c.age()<18) // precondition: Customer is aged 18+ 
    throw CustomerTooYoung;

  ... do something more specific 

  // keep same postcondition check as before
  }
  ...
} 

Here another LSP infringement on post condition only and that completely misuses the design:

class Takeback  extends Transaction {
  Money sellProduct (Customer c, Product p, Quantity q) {
       
    // no precondition, weakens precondition, is ok.   

    ... do something to take the product and reimburse customer  

    // no postcondition, weakens postcondition ;
      return valueCashedIn ;  // negative as it is a reimbursement 
    }
  ...
} 

Hence, we see that strengthening precondition and weakening post condition can in many cases be very different

In short

Strengthening the preconditions and weakening post conditions is not at all the same. These conditions have a different nature regardless of how you enforce them.

In many case, you could weaken preconditions or strengthen pre conditions independently, while both may result failed tests cases when reusing super-type tests for the sub-type.

More details

On LSP and contracts

LSP is about promises made in a contract:

  • preconditions need to be fulfilled before the an operation is performed. If you call the operation when they are not met, it doesn't mean that you have to throw exceptions. It just means that the promises of that operation (invariants, post conditions) are no longer guaranteed.
  • postconditions is what must be guaranteed after the operation is performed, provided the preconditions were initially met. If they are not fulfilled, there's something wrong in the code.

Contracts are not about exception (and vice-versa)

The pre-/post-conditions do not need to be implemented by the type and its subtype. Adding checks and exceptions (inside the the operations, or outside before/after calling them) is defensive programming.

Throwing may be the sign of a failed pre/post-condition. But an exception could as well be a promised behavior of the contract. For example the following contract would make your subtype LSP compliant without changing anything in the code:

Task::Close() ensures that the task is in a closed state if closing is possible or that an exception TaskCannotBeClosed is thrown.

In this case, in your Task no exception is thrown not because of a different contract but because simpler tasks can alway be closed and hence, no need to throw. And throwing for ProjectTask is no longer a strengthening of preconditions.

Example with independent pre and post conditions.

Take the following pseudo-code, which has unrelated pre and post conditions. Please ignore all the other design and style issues, it's just to illustrate the case:

You would have an LSP infringement by strengthening the precondition if you would have a TransactionDangerousGoods that has a precondition that customer is 18+. Absolutely no impact on postcondition.

You would have an LSP infringement on post-condition, if you would have a Takeback class as subtype of Transaction, if the tackeback would misuse the sellProduct() to return the reimbursed amount as negative value.

added 328 characters in body
Source Link
Christophe
  • 80.6k
  • 11
  • 132
  • 199
  • preconditions is about conditions that need to be fulfilled before the an operation is performed. If you call the operation but these conditions are not met, it doesn't mean that you have to throw exceptions. It just means that the promises of that operation (invariants, post conditions) are not guaranteed.
  • postconditions is about conditions that must be guaranteed after the operation is performed, provided the preconditions were initially met. If they are not fulfilled, there's something wrong in the code.

Adding checks and exceptions inside the type's operations, or outside, in the code that uses the type, is defensive programming. It helps sometimes to deduce the contract, but not always. For example, whenever there is a divide in some calculationformula, you may find preconditions that must be fulfilled to avoid the divide by zero to happen, eveneven if you don't explicitly throw.

AlsoBy the way, dependingdepending how you design and document contractcontracts, an Exception may notexception could even be a failed precondition, but on contrary a promised behavior of the contract instead of a failed pre/post-condition(). See for example the following contract that would make your subtype LSP compliant:

Task::Close() ensures that the task is in a closed state if closing is possible or that an exception TaskCannotBeClosed is thrown.

  • preconditions is about conditions that need to be fulfilled before the an operation is performed. If you call the operation but these conditions are not met, the promises of that operation (invariants, post conditions) are not guaranteed.
  • postconditions is about conditions that must be guaranteed after the operation is performed, provided the preconditions were initially met. If they are not fulfilled, there's something wrong in the code.

Adding checks and exceptions inside the type's operations, or in the code that uses the type, is defensive programming. It helps sometimes to deduce the contract, but not always. For example, whenever there is a divide in some calculation, you may find preconditions that must be fulfilled to avoid the divide by zero to happen, even if you don't explicitly throw.

Also, depending how you design and document contract, an Exception may not be a failed precondition, but on contrary a promised behavior.

  • preconditions is about conditions that need to be fulfilled before the an operation is performed. If you call the operation but these conditions are not met, it doesn't mean that you have to throw exceptions. It just means that the promises of that operation (invariants, post conditions) are not guaranteed.
  • postconditions is about conditions that must be guaranteed after the operation is performed, provided the preconditions were initially met. If they are not fulfilled, there's something wrong in the code.

Adding checks and exceptions inside the type's operations, or outside, in the code that uses the type, is defensive programming. It helps sometimes to deduce the contract, but not always. For example, whenever there is a divide in some formula, you may find preconditions that must be fulfilled to avoid the divide by zero to happen, even if you don't explicitly throw.

By the way, depending how you design and document contracts, an exception could even be a promised behavior of the contract instead of a failed pre/post-condition(). See for example the following contract that would make your subtype LSP compliant:

Task::Close() ensures that the task is in a closed state if closing is possible or that an exception TaskCannotBeClosed is thrown.

Source Link
Christophe
  • 80.6k
  • 11
  • 132
  • 199

In short

No the two clauses are not at all the same. They have a different nature regardless of how you enforce them. And both can be infringed separately. Numerous counter-examples invalidate the claim about these being the same thing.

More details

On LSP, contracts and exceptions (the theory)

LSP is about promises made in a contract and not about implementation and exceptions. The preconditions and post conditions do not even need to be implemented by the type and its subtype. Therefore, here a very important distinction:

  • preconditions is about conditions that need to be fulfilled before the an operation is performed. If you call the operation but these conditions are not met, the promises of that operation (invariants, post conditions) are not guaranteed.
  • postconditions is about conditions that must be guaranteed after the operation is performed, provided the preconditions were initially met. If they are not fulfilled, there's something wrong in the code.

Adding checks and exceptions inside the type's operations, or in the code that uses the type, is defensive programming. It helps sometimes to deduce the contract, but not always. For example, whenever there is a divide in some calculation, you may find preconditions that must be fulfilled to avoid the divide by zero to happen, even if you don't explicitly throw.

Also, depending how you design and document contract, an Exception may not be a failed precondition, but on contrary a promised behavior.

Hypothetical counter example (the practice)

But enough theory. Now a counter-example, that uses exceptions for pre and post conditions like you did. Take the following pseudo-code, which has more distinct pre and post conditions. Please ignore all the other design and style issues (I would of course design this very differently, it's just to illustrate the case):

class Transaction {
  ...
  Money sellProduct (Customer c, Product p, Quantity q) {
       
    if (c.age()<12) // precondition: Customer is aged 12+ 
      throw CustomerTooYoung;

    ... // do something 

    if (valueCashedIn < Money(0.00)) // postcondition: amount cashed-in is >=0
      throw InternalErrorOnValue;
    return valueCashedIn ; 
  }
  ...
}

Here a LSP infringement that strengthens the precondition but leaves post condition unchanged:

class TransactionDangerousGoods extends Transaction {
  Money sellProduct (Customer c, Product p, Quantity q) {
       
  if (c.age()<18) // precondition: Customer is aged 18+ 
    throw CustomerTooYoung;

  ... do something more specific 

  // keep same postcondition check as before
  }
  ...
} 

Here another LSP infringement on post condition only and that completely misuses the design:

class Takeback  extends Transaction {
  Money sellProduct (Customer c, Product p, Quantity q) {
       
    // no precondition, weakens precondition, is ok.   

    ... do something to take the product and reimburse customer  

    // no postcondition, weakens postcondition ;
      return valueCashedIn ;  // negative as it is a reimbursement 
    }
  ...
} 

Hence, we see that strengthening precondition and weakening post condition can in many cases be very different