1. 15
  1.  

  2. 9

    Implementing accessors like these in C++ is a huge code smell IMO, especially if you need a setter. In these cases, most of the logic in the setter method should be in the constructor of a specialized type pretty much like Latitude or SphericalCoordinate, but with copy/move constructors and overloaded assignment operators—instead of operator() overloads—just like the rule of three/five tells us to. And of course the member in question should be of that type instead.

    1. 9

      I find that at least 99% of my use of getters is solely to make a class member read-only to the outside world. I wish C++ had a better way to specify, “this member is const to everything except for the class it’s part of”.

      The other 1% are for when I want a class to have a certain interface but want to store the data differently for efficiency reasons.

      1. 2

        I thought when I read this article yesterday that you ought to be able to do that generically and it turns out you can in C++. This is a template that provides private implicit conversions to and from T that are exposed to only one other class:

        class ReadOnly
        {
                friend Owner;
                T val;
                void operator=(T& n)
                {
                        val = n;
                }
                operator T&()
                {
                        return val;
                }
                public:
                T get()
                {
                        return val;
                }
        };```
        
        You can use this to declare a field, which then behaves in methods as if it is of type `T`:
        
        ```c++
        struct Example
        {
                ReadOnly<int, Example> x;
                void use_private_field(int i)
                {
                        x = i;
                        i = x;
                }
        };
        

        The implicit conversion operators let you assign to the int-like field as if it were an int and implicitly read the value as an int. If you try to access it from outside, you can only do so via the get method:

                Example e;
                int i = e.x.get();
                e.x = 12;
        

        The first two lines here are fine, the third is a compiler error.

        The ReadOnly template doesn’t add any size to your class and if you mark the methods on it as always-inline then you will get exactly the same code generated as if you used a raw field type.

        This is a quick-and-dirty implementation, you could design something better with a bit more effort.

      2. 5

        Many objects with getters/setters are just structs with extra steps. As much as Meyer’s gets cited, most people seem to have skipped over the Uniform Access Principle when bringing OOP to languages.

        Getters and setters are a violation of UAP and a major failing of most languages which allow OOP, with the notable exceptions of Ruby, Objective-C and C#.

        1. 2

          Can you expand on what you mean here? I thought the whole argument for getters and setters was to make field access by clients be method calls, to follow UAP?

          The idea being that by making clients do ‘x.getFoo()’ instead of ‘x.foo’ you leave space to refactor without breaking clients later on.

          ie. in what way are getter/setters a violation of UAP?

          In my mind the thing I disagree with is not getters and setters, its UAP thats the problem.

          1. 3

            If I understand correctly “planning ahead” and using getter and setter methods are a workaround for the lack of UAP, it’s being treated as a property of a language not a program. Ruby and Objective-C don’t allow you to access members without going through a method (they force UAP), C# lets you replace member access with methods since it supports member access syntax for methods.

            1. 1

              C# lets you replace member access with methods since it supports member access syntax for methods.

              Python has the same feature (in fact, I’m pretty sure had it first). You can start with a normal member, then replace it with a method via the property decorator and if you want to, implement the full set of operations (get, set, delete) for it without breaking any previous consumers of the class.

              Of course, Python also doesn’t actually have the concept of public versus private members, aside from socially-enforced conventions, but your concern seems to be less whether someone can find the private-by-convention attributes backing the method, and more with whether x.foo continues to work before and after refactoring into a method (which it does, if the new foo() method is decorated with property).

              1. 1

                Of course, Python also doesn’t actually have the concept of public versus private members, aside from socially-enforced conventions

                I’m genuinely curious, as at first glance you seem to be an advocate of this, what’s the benefit of socially-enforced conventions over compiler-enforced privacy? Also, another thing I’ve been curious about not having programmed much in a language with these semantics, how does a language like Python handle something like the following:

                There’s a class I want to extend, so I inherit it. I implement a “private member” __name. The base class also implemented __name, does my definition override it?

                I’ve been wondering about that because if that’s the case, it seems like that would require people to know a lot of implementation details about the code their using. But for all I know, that’s not the case at all, so I’d be happy to hear someone’s perspective on that.

                1. 3

                  There’s a class I want to extend, so I inherit it. I implement a “private member” __name. The base class also implemented __name, does my definition override it?

                  It doesn’t. Double underscores mangle the attribute name in this case by prepending _<class name> to the original attribute name, which obfuscates external access. The attribute is only accessible by its original name in the class they are declared.

                  Python docs: https://docs.python.org/3/tutorial/classes.html#private-variables

                  1. 2

                    I’m genuinely curious, as at first glance you seem to be an advocate of this, what’s the benefit of socially-enforced conventions over compiler-enforced privacy?

                    It was my first day at the new job. I’d been shown around the office, and now I was at my new desk, laptop turned on and corporate email set up. I’d been told my first ticket, to help me get to know the process, would be changing the user-account registration flow slightly to set a particular flag on certain accounts. Easy enough, so I grabbed a checkout of the codebase and started looking around. And… immediately asked my “onboarding buddy”, Jim, what was going on.

                    “Well, that’s the source code”, he said. “Yeah, but is it supposed to look like that? “Of course it is, it’s encrypted, silly. I though you were supposed to be coming in with years of experience in software development!” Well, I said I’d seen some products that shipped obfuscated or encrypted code to customers, but never one that stored its own source that way. “But this way you have proper access control! When you’re authorized to work on a particular component, you reach out to Kevin, who’s the senior engineer on our team, and he’ll decrypt the appropriate sections for you, then re-encrypt when you’re done. That way you never see or use any code you’re not supposed to know about. It’s called Data Hiding, and it’s a fundamental part of object-oriented programming. Are you sure you’ve done this before?”

                    I sighed. And then recognition dawned. “Hey, wait”, I said, “this isn’t really encrypted at all! It’s just ROT13! Look, here this qrs ertvfgre_nppbhag is actually just def register_account…”

                    THWACK!

                    I’d been pointing at the code on my screen excitedly, and didn’t notice someone sneaking up behind me. Until he whacked me across the fingers, hard, with a steel ruler. “YOU ARE NOT AUTHORIZED TO READ THAT CODE!” he yelled, and then walked away.

                    “Who was that?”

                    “That was Kevin, the senior engineer I told you about. You really should be more careful, you’ll get written up to HR for an access violation. And if you accumulate three violations you get fired. Maybe they’ll let this one slide since you’re new and obviously inexperienced.”

                    “But how does anyone get anything done here?”, I asked.

                    “I told you – you ask Kevin to decrypt the code you’re supposed to work on.”

                    “But what if I need to use code from some other part of the codebase?”

                    “Then Kevin will liaise with senior engineers on other teams to determine whether you’re allowed to see their code. It’s all very correct according to object-oriented design principles!”

                    I goggled a bit. Jim finally said, “Look, it’s obvious to me now that you’ve never worked somewhere that followed good practices. I’m not going to tell on you to HR for whatever lies you must have put on your résumé to get hired here, but maybe you could tell me what you used to do so I can help you get up to speed on the way professionals work.”

                    So I explained that at previous jobs, you could actually see all the code when you checked it out, and there was documentation explaining what it all did, how to perform common tasks, what APIs each component provided, and so on, and you’d look things up and write the code you needed to write for your tasks and file a pull request that eventually got checked in after review.

                    Now Jim was goggling at me. “But… what if someone used the code in a way the original team didn’t want it to be used? How would you protect against that?”

                    “Well, there were conventions for indicating and documenting which APIs you were committing to support and maintain, and the policy was anyone could use those APIs any time. But if you needed something that wasn’t provided by any supported API, you’d talk to the team that wrote the component and work something out. Maybe they would say it was OK to use a non-supported API as long as you took responsibility to watch for changes, maybe they’d work with you to develop a new supported API for it, or come up with a better solution.”

                    Jim couldn’t believe what I was telling him. “But… just knowing which team wrote some other code is a violation of the Principle of Least Knowledge! That’s a very important object-oriented principle! That’s why everything that crosses boundaries has to go through Kevin. Why, if you could just go talk to other teams like that you might end up deciding to write bad code that doesn’t follow proper object-oriented principles!”

                    I tried my best to explain that at my previous jobs people trusted and respected each other enough that there wasn’t a need for fanatically-enforced controls on knowledge of the code. That we did just fine with a social-convention-based system where everybody knew which APIs were supported and which ones were “use at your own risk”. That there certainly weren’t senior engineers wandering among the desks with steel rulers – that senior engineers had seen it as their job to make their colleagues more productive, by providing tools to help people write better code more quickly, rather than being informational bottlenecks who blocked all tasks.

                    After I finished explaining, Jim shook his head. “Wow, that sounds awful and I bet the code they produced was pretty bad too. I bet you’re glad to be out of those old jobs and finally working somewhere that does things right!”

                    1. 1

                      So just to make sure I’m following, your argument is that if you need to use something that’s not included in the public API, compiler-enforced privacy requires you to talk to the team that developed the code if you need an extension to the API, while convention-enforced privacy requires that in order to make sure you don’t break anything you… talk to the team that developed the code so that you can work out an extension to the API?

                      1. 1

                        My argument is that in languages with enforced member-access/data-hiding, I can’t even think about using a bit of API that hasn’t been explicitly marked as available to me. If I try it, the compiler will thwack me across the hand with a steel ruler and tell me that code is off-limits. My only options are to implement the same thing myself, with appropriate access modifiers to let me use it, or somehow convince the maintainer to provide public API for my use case, but even that won’t happen until their next release.

                        In Python, the maintainers can say “go ahead and use that, just do it at your own risk because we haven’t finalized/committed to an API we’re willing to support for that”. Which really is what they’re saying when they underscore-prefix something in their modules. And Python will let me use it, and trust that I know what I’m doing and that I take on the responsibility. No steel rulers in sight.

                        A lot of this really comes down to Python being a language where the philosophy is “you can do that, but it’s on you to deal with the consequences”. And that’s a philosophy I’m OK with.

                2. 1

                  I have mixed feelings about UAP, because I want to know when accessing a field is a trivial operation, and when it can run some expensive code.

                  If a field is just a field, then I know for sure. If it could be a setter/getter, then I have to trust that author of the class isn’t doing something surprising, and will not make it do something surprising in the future.

                  1. 1

                    You can’t know that in languages like Java which default to get_X() and set_Y() for even the simplest field access, if only to not break their interface when they need to add a simple range guard.

                    Languages without UAP will go to such lengths to emulate it using methods that you will seldom see a bare field exposed, at which point you can no longer know if a simple getter will return a local field or fire nuclear missiles.

                    1. 1

                      Yeah, IMO it’s a terrible idea for multiple reasons.

                      One, like you’re saying, it gives up a super powerful tool for improving readability in client code. If you’re using a language that has “property accessor” nonsense, every access to fields provided by a library may - for all you know - throw IO exceptions or have any other arbitrary behavior. With method calls being explicitly separate, you add a huge aid in optimizing for readers by reducing the number of possible things that can happen.

                      Two, it makes library authors think they can swap client field access for computation without breaking backwards compatibility, which is some sort of post-modernist academic fiction.