On Apr 4, 2012, at 6:27 PM, Kevin Smith wrote:
> ...
> Will private methods, implemented with private name objects, provide us with
> the same guarantees? Let's refactor again, this time using a private name
> for the new method:
>
> // Assume that there exists a "const M3 = Name.create();" somewhere above
>
> M1() {
> <A>
> <B>
> this[M3]();
> }
>
> M2() {
> <D>
> this[M3]();
> <E>
> }
>
> [M3]() {
> <C>
> }
>
> Since M3 is not visible to any code that does not also have a reference to
> the M3 private name, we can say that this refactoring provides the same
> public interface. But is it functionally identical?
>
> Let's assume that prior to the refactoring, M1 and M2 were generic methods.
> That is, they reference only properties of |this| which correspond to some
> subset S of the public interface of the class. Any other object which
> implements S can also be used with M1 and M2 (like the generic methods
> defined on Array.prototype).
>
> After the refactoring shown above, there's no possibility that M1 and M2 can
> be generic, since they access a privately named property of |this| which by
> definition is not a part of the public interface of the class. So in this
> sense, the refactored code is not functionally identical to the original code.
I don't think this is really a problem or if it is, that the problem isn'
specific to private name based procedural decomposition. Consider such a
"generic method". How can you actually use such a method with "any other
object that implements S". In ES there are basically two ways that are pretty
much semantically equivalent. You access the M1 function value and install it
as a method (with a name of your choosing) in the other object and invoke it as
a method. Or, you use Function.prototype.call (or apply) to invoke the
functions with the other object as the this value. Either technique only works
if M1 is completely self contained or is only dependent upon the S interface.
The manner in which it is not self contained is really important. Any
mechanism that depends upon dynamic coupling to state or behavior that is not
part of S breaks the generic-ness of M1. A reference to a Java-like private
declaration would still have the same problem (at least for state access) as
the "other object" would not have the proper shape to support that reference.
If you want to refactor the implementation of M1 you must keep it "self
contained" in order to keep it generic. A captured lexical reference to a
helper function does that.
>
> If we wanted to preserve identical functionality, we could define M3 as a
> regular function and call it using the call method of Function.prototype:
>
> // Somewhere *before* the class definition we have:
> function M3() {
> <C>
> }
>
> ...
>
> M1() {
> <A>
> <B>
> M3.call(this);
> }
>
> M2() {
> <D>
> M3.call(this);
> <E>
> }
>
> The above strategy works, but (a) explicitly calling M3 via call() is
> awkward, and (b) it defeats the purpose of an elegant class syntax if we must
> refactor code into functions outside of the class definition.
You don't have to use call, you can just define an additional argument for M3
(which should perhaps be called F3 as it isn't actually a method). A principle
of refactoring is that valid refactorings are based upon well defined rules
that preserve the semantics of the original code across the transformation.
The transformations/rule sets are typically given meaningful names. In this
case, the refactoring you want to perform is not "Extract Method" but something
else, perhaps called "Extract helper function from method". Just like "extract
method", the refactoring would turn any references to method arguments or local
declarations (outside of the extracted fragment) into explicit parameters of
the extracted function. However, if the fragment contained any "this"
references, it must also provide an explicit parameter for transmitting the
this value. The proper refactoring would be:
M1() {
<A>
<B>
F3(this, /*any local values referenced by <C>*/);
}
M2() {
<D>
F3(this, /*any local values referenced by <C>*/);
<E>
}
// ES scoping rules permit this to appear either *before*or *after* the
class definition:
function F3(baseThis, /* any free variables in <C>*/ ) {
<C with all occurrences of "this" replaced with "baseThis">
}
Allen
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss