Reflective programming (or reflection) occurs when your code modifications itself on the fly. For instance, a technique of a category, once we name it, amongst different issues provides a brand new methodology to the category (often known as monkey patching). Java, Python, PHP, JavaScript, you identify it—all of them have this “highly effective” characteristic. What’s wrong with it? Effectively, it’s slow, dangerous, and exhausting to learn and debug. However all that’s nothing in contrast with the coupling it introduces to the code.
![Wheelman (2017) by Jeremy Rush](https://www.yegor256.com/images/2022/02/wheelman.jpg)
There are lots of conditions when reflection can “assist” you. Let’s undergo all of them and see why the coupling they add to the code is pointless and dangerous.
Sort Checking and Casting
Right here is the code:
public int sizeOf(Iterable gadgets) {
return ((Assortment) gadgets).dimension();
}
I’m unsure everyone would agree that that is reflection, however I imagine it’s: we examine the construction of the category at runtime after which make a name to the strategy dimension()
which doesn’t exist within the Iterable
. This methodology solely “reveals up” at runtime, once we make a dynamic shortcut to it within the bytecode.
Why is that this unhealthy, other than the truth that 1) it’s slow, 2) it’s extra verbose and so less readable, and three) it introduces a brand new level of failure because the object gadgets
will not be an occasion of sophistication Assortment
, resulting in MethodNotFoundException
?
The most important drawback the code above causes to all the program is the coupling it introduces between itself and its shoppers, for instance:
public void calc(Iterable<?> record) {
int s = sizeOf(record);
System.out.println("The dimensions is " + s);
}
This methodology may go or it might not. It can rely on the precise class of record
. Whether it is Assortment
, the decision to sizeOf
will succeed. In any other case, there shall be a runtime failure. By trying on the methodology calc
we will’t inform what’s the proper method to deal with record
to be able to keep away from runtime failure. We have to learn the physique of sizeOf
and solely then can we modify calc
to one thing like this:
public void calc(Iterable<?> record) {
if (record instanceof Assortment) {
int s = sizeOf(record);
System.out.println("The dimensions is " + s);
} else {
System.out.println("The dimensions is unknown");
}
}
This code appears to be OK up to now. Nevertheless, what’s going to occur when sizeOf
modifications its implementation to one thing like this (I took it from this article about casting):
public int sizeOf(Iterable gadgets) {
int dimension = 0;
if (gadgets instanceof Assortment) {
dimension = ((Assortment) gadgets).dimension();
} else {
for (Object merchandise : gadgets) {
++dimension;
}
}
return dimension;
}
Now, sizeOf
completely handles any sort that’s coming in, whether or not it’s an occasion of Assortment
or not. Nevertheless, the strategy calc
doesn’t know concerning the modifications made within the methodology sizeOf
. As an alternative, it nonetheless believes that sizeOf
will break if it will get something other than Assortment
. To maintain them in sync we now have to do not forget that calc
is aware of an excessive amount of about sizeOf
and should modify it when sizeOf
modifications. Thus, it’s legitimate to say that calc
is coupled with sizeOf
and this coupling is hidden: likely, we’ll overlook to change calc
when sizeOf
will get a greater implementation. Furthermore, there might be many different locations in this system much like calc
, which we should bear in mind to change when the strategy sizeOf
modifications. Clearly, we’ll overlook about most of them.
This coupling, which is a giant maintainability concern, was launched because of the very existence of reflection in Java. If we had not been in a position to make use of instanceof
operator and sophistication casting (or didn’t even have them), the coupling wouldn’t be attainable within the first place.
Forceful Testing
Contemplate this code:
class E-book {
non-public String creator;
non-public String title;
E-book(String a, String t) {
this.creator = a;
this.title = t;
}
public void print() {
System.out.println(
"The ebook is: " + this.identify()
);
}
non-public String identify() {
return this.title + " by " + this.creator;
}
}
How would you write a unit check for this class and for its methodology print()
? Clearly, it’s nearly inconceivable with out refactoring the category. The tactic print
sends textual content to the console, which we will’t simply mock because it’s “static.” The appropriate manner could be to make System.out
injectable as a dependency, however a few of us believe that reflection is a greater possibility, which might permit us to check the non-public methodology identify
instantly, with out calling print
first:
class BookTest {
@Check
void testNamingWorks() {
E-book b = new E-book(
"David West", "Object Considering"
);
Technique m = ebook.getClass().getDeclaredMethod("identify");
m.setAccessible(true);
assertThat(
(String) m.invoke(ebook),
equalTo("Object Considering by David West")
);
}
}
You can too use PowerMock Java library to do many “lovely” issues with non-public strategies.
The issue with this check is that it’s tightly coupled with the thing it exams: the check is aware of an excessive amount of concerning the class E-book
. The check is aware of that the category incorporates a non-public methodology identify
. The check additionally is aware of that the strategy identify
will sooner or later be referred to as by the strategy print
. As an alternative of testing print
the check exams what it’s not supposed to concentrate on: the internals of the category E-book
.
The primary goal of a unit check is to be a “security web” for us programmers making an attempt to change the code that was written earlier or a lot a lot earlier: if we break something, the exams give us a well timed sign, “highlighting” the place the place the code was damaged. If nothing is highlighted and the exams are inexperienced I can proceed modifying the code. I depend on the knowledge from my exams. I belief them.
I take the category E-book
and wish to modify it, merely making the strategy identify
return StringBuilder
as a substitute of String
. It’s a reasonably harmless modification, which can be needed for efficiency issues. Earlier than I begin making any modifications, I run all exams (it’s a good practice) they usually all cross. Then I make my modifications, anticipating no exams to fail:
class E-book {
// ...
public void print() {
System.out.println(
"The ebook is: " + this.identify().toString()
);
}
non-public StringBuilder identify() {
return new StringBuilder()
.append(this.title)
.append(" by ")
.append(this.creator);
}
}
Nevertheless, the check BookTest
will fail, as a result of it expects my class E-book
to have methodology identify
which returns String
. If it’s not my check or I wrote it a very long time in the past, I might be pissed off to be taught this truth: the check expects me to write down my non-public strategies just one particular manner. Why? What’s incorrect with returning StringBuilder
? I might assume that there’s some hidden purpose for this. In any other case, why would a check demand something from a non-public implementation of a category? Very quickly, after some investigation I might discover out that there isn’t any purpose. It’s simply an assumption the check made concerning the internals of E-book
and this assumption has no causes other than “We didn’t have time to refactor the category and make System.out
injectable.”
By the best way, this testing strategy is known because the “Inspector” check anti-pattern.
What would I do subsequent? I must roll again my modifications after which begin refactoring the check and the category, to be able to eliminate this assumption. Nevertheless, altering the check and on the identical time altering principal code is, I believe, a dangerous practice: likely I’ll introduce some new bugs.
The exams are usually not a “security web” for me anymore. I can’t belief them. I modify the code and I do know that I didn’t break something. Nevertheless, the check provides me a pink sign. How can I belief it if it lies in such a easy state of affairs?
This coupling between the unit check BookTest
and the category E-book
wouldn’t occur if it was not attainable to make use of reflection within the first place. If no one had the power to succeed in the non-public methodology in any manner, the Inspector anti-pattern in unit exams wouldn’t be attainable.
Of course, life could be even higher if we additionally didn’t have non-public strategies.
Factories
Right here is how a typical factory may go:
interface Operator {
int calc(int a, int b);
}
// It is a Manufacturing unit Technique:
Operator make(String identify) {
attempt {
return Class.forName("Op" + identify);
} catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
}
The factory method is make
. It expects the identify of the “operator” to be supplied after which, utilizing Class.forName()
from the Java Reflection API, constructs the identify of the category, finds it within the classpath, and makes an occasion of it. Now, say there are two lessons each implementing the interface Operator
:
class OpPlus implements Operator {
int calc(int a, int b) {
return a + b;
}
}
class OpMinus implements Operator {
int calc(int a, int b) {
return a - b;
}
}
Then we use them, first asking our manufacturing facility methodology to make objects from operator names:
int outcome = make("Plus").calc(
make("Minus").calc(15, 3),
make("Minus").calc(8, 7)
);
The outcome
shall be 13.
We’d not be capable of do that with out reflection. We must do that as a substitute:
int outcome = new OpPlus().calc(
new OpMinus().calc(15, 3),
new OpMinus().calc(8, 7)
);
In the event you ask me, this code appears to be like way more readable and maintainable. To begin with, as a result of in any IDE that allows code navigation it could be attainable to click on on OpMinus
or OpPlus
and instantly bounce to the physique of the category. Second, the logic of sophistication discovering is supplied out-of-the-box by JVM: I don’t have to guess what occurs when make("Plus")
is known as.
There are a number of the explanation why folks love static factories. I don’t agree with them. This blog post explains why. With out reflection it wouldn’t be attainable to have static factories in any respect and the code could be higher and extra maintainable.
Annotations
In Java you possibly can connect an annotation (an occasion of a DTO-ish interface) to a category (or a component of it like a technique or an argument). The knowledge from the annotation can then be learn at runtime or compile time. In trendy frameworks like Spring this characteristic is steadily used to be able to automate objects wiring: you simply connect some annotations to your lessons and the framework will discover them, instantiate them, place them right into a DI container, and assign to different objects’ attributes.
I’ve said it earlier that this very mechanism of discovering objects and routinely wiring them collectively is an anti-pattern. I’ve additionally said earlier that annotations are an anti-pattern. Neither dependency injection containers, not auto-wiring, nor annotations would exist if there was no reflection. Life could be significantly better and Java/OOP a lot cleaner.
The shoppers of annotated objects/lessons are coupled with them, and this coupling is hidden. An annotated object can change its interface or modify annotations and the code will compile simply advantageous. The issue will floor solely later at runtime, when the expectations of different objects gained’t be happy.
Serialization
When programmers don’t perceive object-oriented paradigm, they make DTOs as a substitute of proper objects. Then, to be able to switch a DTO over a community or put it aside to a file, they serialize or marshall them. It’s normally finished by a particular serialization engine, which takes a DTO, breaks all attainable encapsulation limitations, reads the values of all of its fields, and packages them into, say, a chunk of JSON.
With a view to let the serialization engine break encapsulation limitations, a programming language has to have reflection. First, as a result of some fields of a DTO could also be non-public and thus accessible solely by means of reflection. Second, even when a DTO is designed “proper” with all needed getters for the non-public fields, nonetheless reflection is required to be able to perceive which getters are current and might be referred to as.
The perspective serialization expresses in direction of objects is similar to what ORM does. Neither of them discuss to things, however as a substitute they stunning “offensively” tear them aside, taking away what’s needed, and leaving the poor objects unconscious. If sooner or later an object decides to alter its construction, rename some fields, or change the varieties of returned values—different objects, which really are coupled with the thing by means of serialization, gained’t discover something. They are going to discover, however solely at runtime, when “invalid information format” exceptions begin floating up. The builders of the thing gained’t have an opportunity to note that their modifications to the interface of the thing have an effect on another locations within the code base.
We are able to say that serialization is a “excellent” methodology of coupling two objects such that neither one will learn about it.
The very idea of object-oriented programming is centered across the precept that an object is king. An object and solely an object could resolve what to do with the info it encapsulates. The existence of this precept and adherence to it helps keep away from runtime errors normally attributable to a easy state of affairs: A makes use of the info coming from B with out telling B the way it’s getting used, then B modifications the format or semantics of the info, and A fails to grasp it.
Clearly, serialization in such an “abusive” manner wouldn’t be attainable, if there was no reflection within the first place. A extra cautious serialization could be attainable and could be used, not by means of reflection however through printers carried out by objects.
To conclude, reflection introduces coupling, which is hidden. That is probably the most harmful sort of coupling, as a result of it’s exhausting to comply with, it’s exhausting to seek out, and it’s exhausting to take away. With out reflection object-oriented design could be a lot cleaner and stable. However even when this characteristic does exist, I recommend you by no means use reflection in your programming language.
Do you utilize reflective programming (reflection)?
— Yegor Bugayenko (@yegor256) June 12, 2022