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