samedi 28 novembre 2015

Best design patterns for refactoring code without breaking other parts

I have some PHP code from an application that was written using Laravel. One of the modules was written quite poorly. The controller of this module has a whole bunch of reporting functions which uses the functions defined inside the model object to fetch data. And the functions inside the model object are super messy.

Following is a list of some of the functions from the controller (ReportController.php) and the model (Report.php)(I'm only giving names of functions and no implementations as my question is design related)

Functions from ReportController.php

questionAnswersReportPdf()
fetchStudentAnswerReportDetail()
studentAnswersReport()
wholeClassScoreReportGradebookCSV()
wholeClassScoreReportCSV()
wholeClassScoreReportGradebook()
formativeWholeClassScoreReportGradebookPdf()
wholeClassScoreReport()
fetchWholeClassScoreReport()
fetchCurriculumAnalysisReportData()
curriculumAnalysisReportCSV()
curriculumAnalysisReportPdf()
studentAnswersReportCSV()
fetchStudentScoreReportStudents()

Functions from Report.php

getWholeClassScoreReportData
getReportsByFilters
reportMeta
fetchCurriculumAnalysisReportData
fetchCurriculumAnalysisReportGraphData
fetchCurriculumAnalysisReportUsersData
fetchTestHistoryClassAveragesData
fetchAllTestHistoryClassAveragesData
fetchAllTestHistoryClassAveragesDataCsv
fetchHistoryClassAveragesDataCsv
fetchHistoryClassAveragesGraphData

The business logic has been written in quite a messy way also. Some parts of it are in the controller while other parts are in the model object.

I have 2 specific questions

a) I have an ongoing goal of reducing code complexity and optimizing code structure. How can I leverage common OOP design patterns to ensure altering the code in any given report does not negatively affect the other reports? I specifically want to clean up the code for some critical reports first but want to ensure that by doing this none of the other reports will break.

b) The reporting module is relatively static in definition and unlikely to change over time. The majority of reports generated by the application involve nested sub-queries as well as standard grouping & filtering options. Most of these SQL queries have been hosed within the functions of the model object and contain some really complex joins. Without spending time evaluating the database structure or table indices, which solution architecture techniques would you recommend for scaling the report functionality to ensure optimized performance? Below is a snippet of one of the SQL queries

$sql = 'SELECT "Parent"."Id", "Parent"."ParentId", "Parent"."Name" as systemStandardName, string_agg(DISTINCT((("SubsectionQuestions"."QuestionSerial"))::text) , \', \') AS "quesions", count(DISTINCT("SubsectionQuestions"."QuestionId")) AS "totalQuestions", case when sum("SQUA"."attemptedUsers")::float > 0 then (COALESCE(round(( ( sum(("SQUA"."totalCorrectAnswers"))::float / sum("SQUA"."attemptedUsers")::float ) *100 )::numeric),0)) else 0 end as classacuracy, case when sum("SQUA"."attemptedUsers")::float > 0 then (COALESCE((round(((1 - ( ( sum(("SQUA"."totalCorrectAnswers"))::float / sum("SQUA"."attemptedUsers")::float ) ) )::float * count(DISTINCT("SubsectionQuestions"."QuestionId")))::numeric,1)),0)) else 0 end as pgain FROM "'.$gainCategoryTable.'" as "Parent" '.$resourceTableJoin.' INNER JOIN "SubsectionQuestions" ON "SubsectionQuestions"."QuestionId" = "resourceTable"."ResourceId" INNER JOIN "Subsections" ON "Subsections"."Id" = "SubsectionQuestions"."SubsectionId" LEFT Join ( Select "SubsectionQuestionId", count(distinct case when "IsCorrect" = \'Yes\' then CONCAT ("UserId", \' \', "SubsectionQuestionId") else null end) AS "totalCorrectAnswers" , count(distinct CONCAT ("UserId", \' \', "SubsectionQuestionId")) AS "attemptedUsers" From "SubsectionQuestionUserAnswers"'; if(!empty($selectedUserIdsArr)){ $sql .= ' where "UserId" IN (' .implode (",", $selectedUserIdsArr).')' ; }else { $sql .= ' where "UserId" IN (' .implode (",", $assignmentUsers).')' ; }

              $sql .= ' AND "AssessmentAssignmentId" = '.$assignmentId.' AND "SubsectionQuestionId" IN ('.implode(",", $subsectionQuestions).') Group by "SubsectionQuestionId"
              ) as "SQUA" on "SQUA"."SubsectionQuestionId" = "SubsectionQuestions"."Id"
              INNER JOIN "AssessmentAssignment"
              ON "AssessmentAssignment"."assessmentId" = "Subsections"."AssessmentId"
              INNER JOIN "AssessmentAssignmentUsers"
              ON "AssessmentAssignmentUsers"."AssignmentId" = "AssessmentAssignment"."Id"
              AND "AssessmentAssignmentUsers"."Type" = \'User\'
                          '.$conditaionlJoin.'
              WHERE      "Parent"."Id" IN ('.implode(',', $ssLeaf).') 
                          '.$conditionalWhere.'
              GROUP BY   "Parent"."Id",
                         "Parent"."ParentId",
                         "Parent"."Name"
              '.$sorter;

    $results = DB::select(DB::raw($sql));

Aucun commentaire:

Enregistrer un commentaire