personal web log written by izabeera and dryobates

vim rope refactoring

Refactoring - extracting methods in vim

by dryobates

When you add new functionalities your methods grows. At some point you want to do some refactoring: move parts of those methods to smaller ones. In Vim with a help of Rope library you can achive it in seconds.

I use python-mode [1] while editing python code. It has a nice built-in checking options e.g. McCabe’s code complexity [2] check. When it shows me that code complexity is high that is good indicator that I should try to divide my methods into smaller ones.

Here is a method which has slightly to high complexity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 class ExampleView(ParentView):

     model = Entry
     form_class = EntryForm

     def post(self, request, *args, **kwargs):
         self.object = None
         self.default_status = 201
         try:
             form_class = self.get_form_class()
             form = self.get_form(form_class)
             if form.is_valid():
                 entry = form.save()
                 entry.paragraph_set.all().delete()
                 paragraphs = self.paragraphs
                 if paragraphs:
                     for i, paragraph in enumerate(paragraphs):
                         Paragraph.objects.create(
                             entry=entry,
                             position=i,
                             text=paragraph['text'])
                 return HttpResponse('OK', status=self.default_status)
             else:
                 response_body = ''
                 for key, values in form.errors.iteritems():
                     response_body += 'Error with field: %s\n' % key
                     for value in values:
                         response_body += ' %s\n' % value
                 return HttpResponse(response_body, status=400)
         except IntegrityError, e:
             return HttpResponse('Entry exists: %s' % e, status=409)

In lines 13-22 there is code run when form is valid. In lines 24-29 is code which is run when form is invalid. As we can name those code then we can move it to separate methods. In Vim with Ropevim [3] select in visual mode (Shift-V) lines 13-22 and type:

:'<,'>RopeExtractMethod

You will be asked for method name. I typed form_valid and pressed Enter and again to perform operation. After that I got:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 class ExampleView(ParentView):

     model = Entry
     form_class = EntryForm

     def post(self, request, *args, **kwargs):
         self.object = None
         self.default_status = 201
         try:
             form_class = self.get_form_class()
             form = self.get_form(form_class)
             if form.is_valid():
                 self.form_valid(form)
                 return HttpResponse('OK', status=self.default_status)
             else:
                 response_body = ''
                 for key, values in form.errors.iteritems():
                     response_body += 'Error with field: %s\n' % key
                     for value in values:
                         response_body += ' %s\n' % value
                 return HttpResponse(response_body, status=400)
         except IntegrityError, e:
             return HttpResponse('Entry exists: %s' % e, status=409)

     def form_valid(self, form):
         entry = form.save()
         entry.paragraph_set.all().delete()
         paragraphs = self.paragraphs
         if paragraphs:
             for i, paragraph in enumerate(paragraphs):
                 Paragraph.objects.create(
                     entry=entry,
                     position=i,
                     text=paragraph['text'])

Any variables that were used in that code block where passed to new method. The same I have done with form_invalid:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 class ExampleView(ParentView):

     model = Entry
     form_class = EntryForm

     def post(self, request, *args, **kwargs):
         self.object = None
         self.default_status = 201
         try:
             form_class = self.get_form_class()
             form = self.get_form(form_class)
             if form.is_valid():
                 self.form_valid(form)
                 return HttpResponse('OK', status=self.default_status)
             else:
                 return self.form_invalid(form)
         except IntegrityError, e:
             return HttpResponse('Entry exists: %s' % e, status=409)

     def form_invalid(self, form):
         response_body = ''
         for key, values in form.errors.iteritems():
             response_body += 'Error with field: %s\n' % key
             for value in values:
                 response_body += ' %s\n' % value
         return HttpResponse(response_body, status=400)

     def form_valid(self, form):
         entry = form.save()
         entry.paragraph_set.all().delete()
         paragraphs = self.paragraphs
         if paragraphs:
             for i, paragraph in enumerate(paragraphs):
                 Paragraph.objects.create(
                     entry=entry,
                     position=i,
                     text=paragraph['text'])

Of course the same can be done with functions.

What to do If you want reverse operation? Put cursor on self.form_valid, type:

:RopeInline

and proceed!

[1]Vim python mode https://github.com/klen/python-mode
[2]McCabe’s cyclomatic complexity http://en.wikipedia.org/wiki/Cyclomatic_complexity
[3]Ropevim - Vim plugin utilising Rope https://bitbucket.org/agr/ropevim
dryobates
dryobates
Jakub Stolarski. Software engineer. I work professionally as programmer since 2005. Speeding up software development with Test Driven Development, task automation and optimization for performance are things that focus my mind from my early career up to now. If you ask me for my religion: Python, Vim and FreeBSD are my trinity ;) Email: jakub@stolarscy.com

Archive

Tag cloud