Extending D's support for object-oriented design with private(this)
Jonathan M Davis
newsgroup.d at jmdavisprog.com
Sat Apr 27 09:31:55 UTC 2024
On Saturday, April 27, 2024 1:31:10 AM MDT NotYouAgain via dip.ideas wrote:
> On Saturday, 27 April 2024 at 06:48:47 UTC, Jonathan M Davis
>
> wrote:
> > ...
>
> What is your solution to the problem below?
>
> Let me guess:
> (1) don't make the mistake of bypassing the public interface in
> the unittest.
> (2) put the unittest in a separate module.
>
> My solution:
> (1) make x and y private(this) and you cannot make a mistake,
> since the compiler will not let you make that mistake.
>
> // ---
>
> module m;
> @safe:
>
> class C
> {
> private int x, y;
>
> invariant()
> {
> assert(x == y);
> }
>
> void modifyX(int val)
> {
> x = val;
> }
>
> void modifyY(int val)
> {
> y = val;
> }
> }
>
> unittest
> {
> C c = new C();
> c.x = 10; // incorrect use of the type.
> }
> // ---
Why is that an incorrect use of the type? Why should I care whether code
within the same module is accessing a private member? It doesn't cause
problems with the public API if code does that. And if I don't want code
within the module to access the private data then I simply won't write code
that does, and if I accidentally write code that does, it's all part of the
code that's internal to the module rather than part of the public API, so
it's an implementation detail.
It won't matter to the code that's using my module whether I have code in
there using a type's private members or not. It might go against some
concept that the programmer has of what OO is supposed to look like, but
ultimately, it's an implementation detail of the module and irrelevant to
any code outside of the module. What matters to the code using your module
is whether it works, not how its internals are organized. You could even
have whole sections of your class' implementation be private functions that
are free functions within the module instead of within the class itself, and
it wouldn't matter to anyone using your class.
Is the issue that the code is bypassing the invariant? If your code screws
up when accessing private members and doesn't make sure that the invariant
is correct before any public member functions are called, then you'll catch
it then, because the invariant will fail. So, it's not like this is going to
result in your code having bugs. And if the code does ensure that the
invariant is correct before any public member functions are called, then
it's doing the right thing with regards to the state of the object, and
there shouldn't be a problem.
And for most unit tests, accessing the private state of an object is a
non-issue (in fact, it's often useful). They need to test that that type and
its functions are working correctly, and for most tests, the fact that they
have access to the private members either helps them do their job, or it's
irrelevant.
It's certainly true that if you want to write a test that is guaranteed to
only be able to access the public API of a type that you can't currently put
that in the module itself, but that's usually only a concern when writing
samples for the documentation (since it's obviously a problem if examples
use private members given that users of your code can't do that), and if
those are documented unit tests, then they have to follow immediately after
the symbol that they're documenting, meaning that private(this) wouldn't
help anyway. Those unittest blocks would have to be in a position where they
would have access to the private members whether you wanted them to or not
in order for the compiler to associate them with the symbols that they're
supposed to document and test.
Similarly, private(this) wouldn't help at all with a documented unittest
block accidentally using a private member of the module rather than of a
particular class or struct. So, if documentation is the concern (and that's
usually the place where it would make sense to actually be worried about
using private symbols in a test), private(this) is not a solution.
And if you really want to prevent tests that aren't documented unit tests
from accessing private members for some reason, to do that with
private(this), you're forced to move them outside of the type, making it so
that they can't be next to what they're testing, making it a somewhat
awkward solution even if it's not as bad as having to move the unit tests
into another module.
So, if there's any feature that might be useful here, it isn't a way to lock
away your class' private members from the rest of the module; it's a way to
tell the compiler that a particular unittest block should be treated as if
it were not within the module with regards to which symbols it can access.
Then you could write documented unit tests where you had to use imports and
the public API rather than having them have access to everything within the
module, and you could then be completely sure that none of your examples
used anything private (whether it was part of the class or not). And if you
really wanted other tests to then not have access to private members for
some reason, you could mark those unittest blocks with the same attribute or
pragma or whatever it is that that feature used to mark the unittest block
as not having access.
But for most code within a module, whether it uses a private member or a
public one is just an implementation detail and is irrelevant to any code
outside of that module. All that the code outside cares about is that the
code works properly, not whether its internals use public or private
members. Now, using a member that you didn't intend to would definitely be a
problem if it behaved differently, but that really doesn't have anything to
do with public vs private, since it could happen with any symbol, and proper
testing will catch that.
- Jonathan M Davis
More information about the dip.ideas
mailing list