Refactoring code that uses the PreProcessor
I got to go to great presentation today about work being done on providing refactorings for C code that get runs through a preprocessor. The presentation covered three interesting areas where refactoring gets difficult and presented high level overviews of how to address each situation. Specifically: conditional compilation regions, macros and #include’s. Conditional compilation (#if and #ifdef’s) make things extremely interesting because it causes a potentially exponential increase in the number of meanings behind your code (consider nested #if’s). On top of that you can have a conditional section which includes no valid code whatsoever. Consider the coder who writes:
#if some_random_name
Now here’s where I explain what this code is doing. I could
a comment, but really I prefer to just use conditionals.
#else
int a = 0;
#endif
When trying to do a refactoring, how do you tell that the “else” is a branch that should be considered as part of the refactoring, while the “if” is not.
With macros you run into all sorts of difficult issues. Say, for example, I have a macro like this:
#define MAKE_ENUM(name) struct name { enum _Enum { }; }; typedef name::_Enum name##Enum;
And I have the following code that uses it:
MAKE_ENUM(TypeAttributes)
TypeAttributesEnum attr;
And I then rename “TypeAttributesEnum” to “MemberAttributesEnum”. You need to be able to go and analyze the macro and realize that in order to get the name change you’ll need to change the macro call to MAKE_ENUM(MemberAttributes).
I bring this up because this is an area where refactorings in VS2k5 are pretty weak. If you have conditional sections in your code then we’ll basically only understand one code path that goes through it all. We’ll refactor accurately through that path, but if you later change your project or compiler setup to change which sections are conditionally brought in (say because you’ve switch your configuration from debug to release).
This is probably something that we’re not going to improve on in this release. Because of the large cost to implanting support for this, and also the feeling that conditional compilation is not something that majority of our customers do we felt it could wait until a later release. However, until then we have to admit that our current refactorings will not provide the complete correctness that you would want on the C# code that some customers will be writing.
Comments
- Anonymous
February 01, 2005
Will you be able to handle the following case - which I use extensively in my C# project:
I have a number of Unit Tests. They are in the same project as the code they are testing, but kept in seperate files. To prevent the unit tests being compiled into release buildinds I have a #if DEBUG and #endif at the top and bottom of each of the Unit Test files. Note that there are no #else clauses.
Now are you saying that if I apply a refactoring (say reorder parameters) to a method that has unit tests the refactoring will only be applied to the unit tests if I happen to have DEBUG selected as my Build mode? If I did the refactoring in Release mode would it then fail to reorder the parameters in the method calls in the unit tests?
I would be quite disappointed if you couldn't cover this case. - Anonymous
February 01, 2005
I think that Cyrus was only talking about C++ here. While he does mention C# in the last paragraph, none of the code that he included in the entry is valid C# - #include and #define are both C++ terms with no equivalent in C#.
My guess would be that C# will handle this ok, since it's much easier to parse C# than it is to parse C++. - Anonymous
February 02, 2005
Samuel Jack: You are correct, we will be unable to handle that case. If you're in release mode then code that is in an #ifdef DEBUG looks to us like it doesn't even exist.
My sincere apologies if this limits the usefulness of refactorings for you.
Andy: Macros and includes are not valid in C#, but conditional compilation is, and that's an area where we currently do not "work". I say it in quotes because how you define it depends on a case by case basis and there are places where we will probably never work (like the example i posted above). - Anonymous
February 02, 2005
Everything marked with the ConditionalAttribute is processed with refactoring, which is a great work around. The main disadvantage of the ConditionalAttribute is that the code will always be emitted to IL. So there is my whish.
I would like to have a overload of the ConditionalAttribute contructor to set a boolean which "generates empty body of condition not met". The compiler could evaluate the attribute at compile time and generate an empty body. When this option is set the body would not be emitted to il. I think this would be a great solution. The conditional code would not be emitted to IL, keeping the assembly smaller. I can work with attributes instead of having to work with #if constructs and refactoring would keep on working.
Maybe a whish for the next release? - Anonymous
February 02, 2005
I am more than a little concerned about the implications of what you're suggesting. It sounds like I could find myself in a situation where I believe that refactorings have succeeded throughout the whole of my project, when in reality they have left part of it untouched. Are there going to be any warnings given that certain parts of code might not have been refactored correctly?
With some refactorings it will be obvious because because the code in the #debug sections would not compile correctly after the refactoring, but with others the code would compile correctly, but its meaning might have changed. For example if I applied a parameter reordering to the the method
void IncreaseSize(int width, int length).
Can you think of any work-arounds in the situation I described in my earlier comment? Unit Tests in another Project perhaps (do refactorings work accross projects?)? Or might VS allow me to only include certain files in a Debug Build and not a release build? - Anonymous
February 03, 2005
Bart: In Whidbey we now have conditional conditional attributes (yes i did say that right). So you can still have conditional code without ifdefs, and it will not get emmitted to IL. - Anonymous
February 03, 2005
Samual: Refactoring do indeed work in multiple projects. We're also considering a warnign that certain regions are not going to be touched during the refactoring. And yes, as with any refactoring it's possible that the meaning of your code might change. Unfortunately instead of the runtime meaning changing (Which we can not do nything about), now the compile time meaning will change.
If this is critical to you please file a bug at msdn.microsoft.com/ProductFeedback so we can see how important this is to customers and decide if we should fix this for the 2k5 release. - Anonymous
March 01, 2005
The comment has been removed