CSC/ECE 517 Fall 2016/E1659. Refactor on the fly calc.rb

From Expertiza_Wiki
Jump to navigation Jump to search

This wiki page is for the description of changes made under E1659 OSS assignment for Fall 2016, CSC/ECE 517.

Peer Review Information

For users intending to view the deployed Expertiza associated with this assignment, the credentials are below:

  • Instructor login: username -> instructor6, password -> password
  • Student login: username -> student5431, password -> password
  • Student login: username -> student5427, password -> password

Expertiza Background

Expertiza is an educational web application created and maintained by the joint efforts of the students and the faculty at NCSU. It’s an open source project developed on Ruby on Rails platform and it’s code is available on Github. Using Expertiza students can bid for a particular project topic and the faculty assigns the same to different groups. Students can track their grades for previous projects and make new submissions for current projects. It also allows students to review each other’s work and improve their work upon this feedback.

Description of the current project

We worked on a module named on_the_fly_calc in this Expertiza based OSS project. We focussed on refactoring some methods which were very complex and also modified the language of the module to make it more ruby-istic. We also worked on refactoring the code to reduce redundancy. Our goal in this project was to make this model easier to read and maintain.

Files modified in current project

A model was modified for this project namely:
1. On_the_fly_calc

On_the_fly_calc

On_the_fly_calc is a module that is included in assignment.rb. What this module does is that it calculates the score for both students and instructors. E.g., when a student clicks “view my score”, Expertiza calculates the review scores for each review. This is called “on the fly” because expertiza stores the review scores based on each question (in answers table), but does not store the total score that a reviewer gives to a reviewee. It directly calculates the total review score for each review, using the score values in the answer table during run-time. Eg: For a set of 4 questions a reviewer gives 5 on 5 points for 2 questions and 4 on 5 points for other 2 questions. So only these points are stored in the database and when a reviewee clicks to see the score given by this particular reviewer, on_the_fly_calc module helps calculate the total score i.e. 90% in this case.

List of changes

The main aim was to reduce the complexity grade of ‘on_the_fly_calc.rb’ module from “F” to “C” in CodeClimate, which was achieved. We worked on the following work items(WIs) in order to refactor the module:
WI1 : Refactor ‘Similar Code’ in lines 34-42 and 62-70 in on_the_fly_calc.rb
WI2 : Prefer ‘each’ over every instance of ‘for loop’
WI3 : Refactor ‘scores’ method to reduce ABC (assignment, branch and condition) size
WI4 : Refactor ‘compute_reviews_hash’ method to reduce ABC (assignment, branch and condition) size
WI5 : Refactor ‘compute_avg_and_ranges_hash’ method to reduce ABC (assignment, branch and condition) size

Solutions Implemented and Delivered

Refactored similar code

Lines 34-42 and 62-70 were similar in compute_reviews_hash method. Hence, a dedicated method ‘calc_review_score’ was defined and this method was called in the both instances inside compute_reviews_hash.

Lines 34-42 in compute_reviews_hash:



Similar code in Lines 62-70:



Refactored method 'calc_review_score' for the two similar codes:



Each loop prefered over For loop

In every instance where ‘for’ loops were used in on_the_fly_calc module, ‘each…do’ was replaced instead. This significantly reduced the Cyclomatic complexity of the code.

The following screen shows the instances where for loops where replaced:



Refactored methods - 'scores', 'compute_reviews_hash', 'compute_avg_and_ranges_hash'

1. a) Method ‘scores’ was very complex, performing too many functions within a single method. Hence, every specific function was broken down into 4 separate smaller methods. This significantly reduced the Assignment, Branch, Condition size for ‘scores’ method.

def scores(questions)
    scores = {}
    score_team = scores[:teams][index.to_s.to_sym]
    score = score_team[:scores]
    scores[:participants] = {}
    participant_score          #New method defined - participant_score
    scores[:teams] = {}
    index = 0
    self.teams.each do |team|
      score_team = {}
      score_team[:team] = team
      if self.varying_rubrics_by_round?
        assess                #New method defined - assess
        calculate_score       #New method defined - calculate_score
        calculate_assessment  #New method defined - calculate_assessment
      else
        assessments = ReviewResponseMap.get_assessments_for(team)
        score = Answer.compute_scores(assessments, questions[:review])
      end
      index += 1
    end
    scores
 end

b) The following 4 methods were created after splitting the first method
i. participant_score

def participant_score
  self.participants.each do |participant|
    scores[:participants][participant.id.to_s.to_sym] = participant.scores(questions)
  end
end

ii. assess

def assess
  self.num_review_rounds.each do |i|
    total_score = 0
    total_num_of_assessments = 0 # calculate grades for each rounds
    grades_by_rounds = {}
    round = grades_by_rounds[round_sym]
    assessments = ReviewResponseMap.get_assessments_round_for(team, i)
    round_sym = ("review" + i.to_s).to_sym
    round = Answer.compute_scores(assessments, questions[round_sym])
    total_num_of_assessments += assessments.size
    total_score += round[:avg] * assessments.size.to_f unless round[:avg].nil?
  end
end

iii. calculate_score

def calculate_score
  score = {}
  score[:max] = -999_999_999
  score[:min] = 999_999_999
  score[:avg] = 0
  grades_by_rounds = {}
  round = grades_by_rounds[round_sym]
  self.num_review_rounds.each do |i|
    round_sym = ("review" + i.to_s).to_sym
    grades_by_rounds = {}
    round = grades_by_rounds[round_sym]
    score[:max] = round[:max] if max_condition
    score[:min] = round[:min] if min_condition
  end
end

iv. calculate_assessment

def calculate_assessment
  if total_num_of_assessments.nonzero?
    score[:avg] = total_score / total_num_of_assessments
  else
    score[:avg] = nil
    score[:max] = 0
    score[:min] = 0
  end
end

c) Complex and long statements or loops were broken into simpler multiple statements.

For example, the unless condition in Lines 132-134 was refactored to Line 127. This reduced the Perceived Complexity of 'scores' method.


2. a) We also refactored method ‘compute_reviews_hash’ which was very complex and modularized individual functionalities into 2 separate methods. b) The following 2 methods were created and called after refactoring:
i. scores_varying_rubrics
ii. scores_non_varying_rubrics

3. Similarly, method ‘compute_avg_and_ranges_hash’ was also refactored and a seperate method calc_contri_score was defined to calculate contributor’s score. This method was then called from ‘compute_avg_and_ranges_hash’.

 The Code Climate Grade Before Refactoring

 The Code Climate Grade After Refactoring

Testing Details

UI Testing

1. Login into Expertiza as a student or an instructor.(You can use the student ids in the peer review section above)
2. Go to an assignment which has some prior reviews for a previous project and click on view scores.
3. The total score for a review is calculated on the fly using this module.
4. You will be able to see the average score that each reviewer gave you on the top right of your page.
5. This ensured that the refactoring done by us didn't break the code and it delivered it's expected functionality.

Unit Testing

Scope for future improvement

Every code has future improvements possible. We have evolved the Code Climate Grade 'F' to a Grade 'C' currently by removing the Cyclomatic complexity, the Perceived Complexity and replaced with the "for" with the 'each' to reduce the size . There are modifications that can still reduce the "ABC" Assignment Branches and Conditions Score as we can include the variables and functions in the "before action clauses", and rename long names with shorter yet appropriate names thus moving towards the score of "A".