It's funny that the book doesn't state it clearly, but the reason why it favours a class hierarchy over if statements inside the same class is probably the Open/Closed principle This widely-known software design rule states that a class should be closed to modification but open for extension.
In your example, adding a new lesson type would mean changing the Lesson class's source code, making it fragile and regression prone. If you had a base class and one derivative for each lesson type, you would instead just have to add another subclass, which is generally considered cleaner.
You can put that rule under the "Conditional Statements" section if you will, however I consider that "signpost" to be a bit vague. If statements are generally just code smells, symptoms. They can result from a wide variety of bad design decisions.
You are right that refactoring code is important. It prevents code rot and improves code. It makes for cleaner code.
But good code is not only clean code, it's code that is correct, and thus by definition contains as few bugs as possible (ideally none). The first goal of your code is to produce its expected result. So if your refactoring is introducing bugs you might want to consider the net effect of those refactorings.
You should refactor code that is tested. If it's not, add tests and then refactor. This way you know you haven't broken anything. This will help mitigate the risks of a similar situation from happening in the future.
As for refactoring introducing bugs, refactoring should not alter the behavior of a program. I will quote from Wikipedia:
disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior
Sadly nowadays, refactoring has come to mean anything from this definition to total rewrites.
I would alter what your team lead said from:
refactor but don't break too many things
To:
refactor and don't break anything
As for finding bugs being the job of the QA, quality isn't someone else's problem. The goal should be that QA finds nothing. Realistically finding as little as possible.
Best Answer
Having too many instance variables is not directly related to duplicate code, or vice versa. This statement, in this generality, is false. One can throw two separate classes with no duplicate code into one class - that produces a new class with unseparated responsibilities and too many instance variables, but still no duplicate code.
But when you find a class with too many responsibilites in real world legacy code, chances are high the programmer who wrote it did not care for clean code or SOLID principles (at least, not at the time when he wrote that code), so its not unlikely you will find other code smells like duplicate code in there.
For example, the "copy-paste reuse" anti-pattern is often applied by copying an old method and making some slight modifications to it, without proper refactoring. Sometimes, to make this work, one has to duplicate a member variable and modify that variable a little bit, too. This might result in a class with too many instance variables (more precise: too many very similar looking instance variables). In such a situation, the similar instance variables maybe an indicator for repeated code elsewhere in the class. However, as you noted, this is an artificial example, and I would not conclude a general rule from it.