I remember having a similar argument with my lecturer when learning C++ at university. I just couldn't see the point of using getters and setters when I could make a variable public. I understand better now with years of experience and I've learned a better reason than simply saying "to maintain encapsulation".
By defining the getters and setters, you will provide a consistent interface so that if you should wish to change your implementation, you're less likely to break dependent code. This is especially important when you classes are exposed via an API and used in other apps or by 3rd parties. So what about the stuff that goes into the getter or setter?
Getters are generally better off implemented as a simple dumbed-down passthrough for access to a value because this makes their behaviour predictable. I say generally, because I've seen cases where getters have been used to access values manipulated by calculation or even by conditional code. Generally not so good if you are creating visual components for use at design time, but seemingly handy at run time. There's no real difference however between this and using a simple method, except that when you use a method, you are generally more likely to name a method more appropriately so that the functionality of the "getter" is more apparent when reading the code.
Compare the following:
int aValue = MyClass.Value;
int aValue = MyClass.CalculateValue();
The second option makes it clear that the value is being calculated, whereas the first example tells you that you are simply returning a value without knowing anything about the value itself.
You could perhaps argue that the following would be clearer:
int aValue = MyClass.CalculatedValue;
The problem however is that you are assuming that the value has already been manipulated elsewhere. So in the case of a getter, while you may wish to assume that something else might be going on when you are returning a value, it is difficult to make such things clear in the context of a property, and property names should never contain verbs otherwise it makes it difficult to understand at a glance whether the name used should be decorated with parentheses when accessed.
Setters are a slightly different case however. It is entirely appropriate that a setter provide some additional processing in order to validate the data being submitted to a property, throwing an exception if setting a value would violate the defined boundaries of the property. The problem that some developers have with adding processing to setters however is that there is always a temptation to have the setter do a little more, such as perform a calculation or a manipulation of the data in some manner. This is where you can get side-effects that can in some cases be either unpredictable or undesirable.
In the case of setters I'm always apply a simple rule of thumb, which is to do as little as possible to the data. For example, I will usually allow either boundary testing, and rounding so that I can both raise exceptions if appropriate, or avoid unnecessary exceptions where they can be sensibly avoided. Floating point properties are a good example where you might wish to round excessive decimal places to avoid raising an exception, while still allowing in range values to be entered with a few additional decimal places.
If you apply some sort of manipulation of the setter input, you have the same problem as with the getter, that it is difficult to allow others to know what the setter is doing by simply naming it. For example:
MyClass.Value = 12345;
Does this tell you anything about what is going to happen to the value when it is given to the setter?
The second example tells you exactly what is going to happen to your data, while the first won't let you know if you value is going to be arbitrarily modified. When reading code, the second example will be much clearer in it's purpose and function.
Am I right that this would completely defeat the purpose of having
getters and setters in the first place and validation and other logic
(without strange side-effects of course) should be allowed?
Having getters and setters isn't about encapsulation for the sake of "purity", but about encapsulating in order to allow code to be easily refactored without risking a change to the class's interface which would otherwise break the class's compatibility with calling code. Validation is entirely appropriate in a setter, however there is a small risk is that a change to the validation could break compatibility with calling code if the calling code relies on the validation occurring in a particular way. This is a generally rare and relatively low-risk situation, but it should be noted for the sake of completeness.
When should validation happen?
Validation should happen within the context of the setter prior to actually setting the value. This ensures that if an exception is thrown, the state of your object won't change and potentially invalidate its data. I generally find it better to delegate validation to a separate method which would be the first thing called within the setter, in order to keep the setter code relatively uncluttered.
Is a setter allowed to change the value (maybe convert a valid value
to some canonical internal representation)?
In very rare cases, maybe. In general, it is probably better not to. This is the sort of thing best left to another method.