CTransaction currently exposes its data members publicly and marks them const. This makes the implementation details of the type part of its interface, while using const data members to enforce immutability has several undesirable consequences.
This PR encapsulates the data members behind public observer functions. The existing users of CTransaction are migrated to the observers, after which the data members are made private and non-const.
The refactoring is deliberately split into three steps:
- Add observer functions corresponding to the existing data members.
- Migrate all users from direct data-member access to the observers.
- Make the data members private.
The migration is automated with a new bitcoin-tidy use-observers check. This both makes the large mechanical change straightforward and provides a reusable tool for similar refactorings.
The split into separate commits allows reviewers to review handwritten changes independently of the automated changes. It also simplifies rebasing: if the mechanical migration conflicts, individual files can be reset to their new base versions and the migration tool can be run on them again. This works because the old interface remains available until the final commit, so the reset code is still valid while the mechanical migration is reapplied.
This PR does not change the semantics of CTransaction. In particular, regular value semantics are intentionally left for a subsequent change.
Why Encapsulation? Why CTransaction?
Public data members make the representation of a type part of its API. Once access goes through observers, the representation can change without requiring its users to change as well.
This establishes a clean boundary between the transaction's data and functionality that operates on it. In particular, it allows serialization/deserialization and other functionality to be separated from the type itself in subsequent refactorings.
This work is part of the broader interface-segregation direction described in #35904, but is not a prerequisite for the stateless validation library proposed there. The encapsulation has value independently of that work.
CTransaction is a good type to start this migration with: it is widely used, its interface is relatively simple, and its data members are already effectively immutable. This allows the migration to establish the pattern and tooling without first requiring changes to the semantics of the type.