[ENIAC Thesis Award] You can (not) rename: Introduction to Negotiated Renaming
On average, 70% of any codebase is identifiers. Hence, one of the most used techniques of code refactoring is renaming. Renaming is the process of globally and systematically changing the name of an identifier within the code to better represent its purpose. Symbol renaming is widely supported in most code editors and integrated development environments (IDEs). However, most, if not all, of the current renaming flows are basic and linear, or with a low level of negotiation. Negotiating is the act of trying to achieve a compromise between the transformation engine and its user, giving the user more flexibility and the engine more adaptability.
In this project, we experimented with introducing proper negotiation into the refactoring flow, starting with the rename operation, by formulating a negotiation scheme and expressing it through a custom domain-specific language (DSL).
Background
A living (natural and software) language will (almost) surely evolve from its initial form along with its grammar to reflect the needs of its users. A grammar evolution can be viewed as a model transformation, which can be further expressed by a sequence of transformation operations. In most cases, the transformation process is linear in the sense that it provides zero degrees of negotiation (Figure 1), which will report an error and halt whenever a transformation command is assessed to be inapplicable or vacuous (i.e., a transformation leads to zero changes.).
On the other hand, code refactoring is the process of purposefully restructuring a piece of existing code, deliberately and systematically altering its internal structure to improve its quality, while maintaining its external behavior. Underneath, code refactoring is done by performing a chain of transformation operations on the code elements with the same principles as grammar transformation. Here, we are particularly interested in the rename operation. Renaming in short is the process of globally and systematically changing the name of a non-terminal symbol within the code to better represent its purpose.
Extensive studies have been conducted to investigate the nature of identifier renaming and why developers rename, showing that the majority of developers want and need more assistance with naming identifiers. While the current approaches have some back and forth with the user, presenting the renaming opportunities and suggestions, the negotiation is only introduced after the initial renaming operation is performed. This leaves the actual rename operation initiated on the initial target identifier with little to no negotiation. Our work aims to complement existing methods, rather than replace them, by introducing proper negotiation directly into the transformation process itself.
Negotiation strategies
We dissected the construction of a rename operation and formulated a negotiation scheme to fit it, based on the previous works of Zaytsev [1, 2]. More formally, a rename command can be written as: rename: a → b
We found out that there are two negotiable main arguments in a rename command, namely the source identifier a
and the new target name b
. Under certain conditions of definedness, validity, and equality of these two arguments, a rename command can be: applicable, vacuous, or inapplicable. For each scenario, we examined and curated a combination of strategies to introduce negotiation into the renaming flow.
For applicable rename commands, these strategies are already acceptable. However, this is where other research on automatic (re)naming recommenders and identifier name quality can be applied to provide suggestions on naming improvements and opportunities. Regarding vacuous rename commands, although they will result in zero changes to the source code, failing the entire command sequence when facing one does not help. Thus, it is more logical to accept them and move on to the next command.
Rename commands are inapplicable because of their arguments. For the source identifier, it can be undefined or invalid, while for the target name, it can be invalid, a collision, or a conflict. All these cases can benefit from some suggestions. However, unlike others, a collision does not cause a fatal error that halts the transformation, which can still be considered acceptable with an extra warning.
Overall, we defined two categories of negotiation strategies: active and passive. Passive negotiation strategies do not require user intervention while the command is being processed. This includes strategies that can be handled internally by the engine and those that can be embedded directly in the command:
- Embedded alternatives: Allows embedding different alternatives in the command. The engine will go through each one in the defined order and stop at the first acceptable command.
- Auto-select applicable name: The user explicitly requests the engine to select any applicable target name for the command.
- Vacuous acceptance: Produces a vacuous warning and moves on to the next command.
- Collision acceptance: Produces a collision warning and still applies this transformation if it is acceptable by the language.
Conversely, active negotiation strategies require user inquiry. While the command is processed, if the engine encounters any issue, it will prompt for user input. The degree of actively asking the user is limited by the inquiry level to control the tradeoff between automation and interactivity. This includes:
- Suggestions: Present to the user a list of applicable suggestions.
- Manual negotiation: Ask the user for manual input to recover the command. Alternatively, the user can also provide a new command to replace the current one.
These strategies combined to form a negotiation scheme (Figure 2) that enables negotiation in the renaming flow. This scheme is not only useful for the rename operation but also for any operation that contains names that can apply similar name negotiating strategies to recover from some cases of inapplicability.
Negomancy — A language for negotiation
Negomancy was created to express negotiation for renaming. Contrasting to a general-purpose language, a DSL is much simpler to learn and use due to its more limited scope, focusing only on a specific domain, hence terms can be expressed at the abstraction level of the problem domain.
The main design philosophy of Negomancy syntax is concise and intuitive. Statements in Negomancy can be either a command to request the engine to perform some action or a reply to respond to the engine’s prompts. Each statement is a combination of case-insensitive keywords and string arguments separated by whitespaces.
The negotiation features of Negomancy are closely based on the strategies in the established negotiation scheme above. At the syntax level, some negotiation strategies are not available, namely the vacuous acceptance and collision acceptance strategies. In addition, Negomancy also provides some extra negotiation for case styling the names using the same principle as the embedded alternatives strategy.
Negomancy is a special language that depends on and leverages the extensibility of the implementing platform to perform checks and execute the refactoring transformation. To demonstrate that a language like Negomancy is feasible, we built Negomancer, a plugin that serves as a proof-of-concept of negotiated refactoring built on top of the IntelliJ Platform written in Kotlin. Negomancer partially adapts the proposed DSL Negomancy along with its features and negotiation abilities. Negomancer is presented as a toolwindow (Figure 3), a collapsible side panel that the user can access on demand, or it will automatically open when a rename action is triggered from the source code.
To evaluate the expressiveness of Negomancy and ensure that it faithfully represents the domain of interest, we have performed an ontological analysis. The ontological analysis was done by mapping and comparing the proposed DSL and an independently designed ontology of the domain of negotiation on the rename refactoring operation. The results of this evaluation showed that for most parts Negomancy expresses the domain with completeness and clarity. The main issue remaining is the lack of a complete (and preferably concise) way to represent and refer to a specific identifier, which deserves its own research and falls beyond the scope of this project.
References
Sources used during this research can be found in the thesis as published by Nhat. The thesis is available at https://essay.utwente.nl/88515.
[1] V. Zaytsev, Negotiated Grammar Transformation, in: J. D. Lara, D. D. Ruscio, A. Pierantonio (Eds.), Post-proceedings of the Extreme Modeling Workshop (XM 2012), XM ’12, Association for Computing Machinery, New York, NY, USA, 2012, p. 27–32. doi:10.1145/2467307.2467313.
[2] V. Zaytsev, Negotiated Grammar Evolution, Special issue on Extreme Modeling of The Journal of Object Technology (JOT) 13 (2014) 1:1–22. doi:10.5381/jot.2014.13.3.a1.