8. 채점 결과 출력하기

8. 채점 결과 출력하기

채점이 끝났다면, 어떻게 보여줄까?


이제 백엔드에서 채점 엔진을 실행하고, 결과를 받아 보여주자!


채점 기준은 잘 저장된다

Policy Detail


데이터베이스로 NoSQL인 MongoDB를 사용했는데, 프론트엔드 측에서 채점 결과를 설정하고 백엔드로 보내면,

백엔드는 채점 기준을 데이터베이스에 저장하고, 채점 시 토큰을 검색해 기준을 엔진에 전달한다.


엔진은 AST를 사용한 정적 분석 및 기준의 테스트 케이스를 돌려 보고, 감점 요소를 체크하여 다시 백엔드로 보낸다.

백엔드는 채점 결과를 다시 데이터베이스에 저장하고 필요한 정보만 프론트엔드에게 넘겨준다.


이 때 필요한 정보란, 피드백 제공 여부와 상관없이 보여줄 정보 (학생이 입력한 학번, 클래스 이름 등)와,

피드백 제공 여부와 연관된 채점 정보들 (감점 점수, 획득 점수 등)을 말한다.



받은 정보 분해하기

이전 파트에서 Route를 이용해 detail이란 이름으로 채점 결과를 넘겨주었다.


// file: 'ResultProcess.js'

...

function ResultProcess (props) {
    const classesStyle = useStyles();
    const classesLayout = useStylesLayout();

    if (props.location.state === undefined) {
        props.history.push('/error');
        return null;
    } 
    
    else {
    const results = props.location.state.detail;

    const existsInPolicy = {
        feedback: results.isDirect === 'true' ? true : false,
        count: results.count !== undefined ? (results.count.deductedPoint === 0 ? true : false) : undefined,
        compiled: results.compile !== undefined ? (results.compile.deductedPoint === 0 ? true : false) : undefined,
        inputs: results.runtimeCompare !== undefined ? (results.runtimeCompare.deductedPoint === 0 ? true : false) : undefined,
        classes: results.classes !== undefined ? (results.classes.deductedPoint === 0 ? true : false) : undefined,
        packages: results.packages !== undefined ? (results.packages.deductedPoint === 0 ? true : false) : undefined,
        custexc: results.customException !== undefined ? (results.customException.deductedPoint === 0 ? true : false) : undefined,
        custstr: results.customStructure !== undefined ? (results.customStructure.deductedPoint === 0 ? true : false) : undefined,
        interfaces: results.inheritInterface !== undefined ? (results.inheritInterface.deductedPoint === 0 ? true : false) : undefined,
        superclass: results.inheritSuper !== undefined ? (results.inheritSuper.deductedPoint === 0 ? true : false) : undefined,
        overriding: results.overriding !== undefined ? (results.overriding.deductedPoint === 0 ? true : false) : undefined,
        overloading: results.overloading !== undefined ? (results.overloading.deductedPoint === 0 ? true : false) : undefined,
        thread: results.thread !== undefined ? (results.thread.deductedPoint === 0 ? true : false) : undefined,
        javadoc: results.javadoc !== undefined ? (results.javadoc.deductedPoint === 0 ? true : false) : undefined,
        encapsulation: results.encapsulation !== undefined ? (results.encapsulation.deductedPoint === 0 ? true : false) : undefined,
    };
    
    ...
    


백엔드에서 보내주는 detail을 뜯어봤더니, 채점 기준에 포함되지 않는 것은 null로 처리되어 있었다.

이를 TS로 처리하려고 하다가 계속 오류가 나서, JS로 돌렸다.😢

Route를 통해 detail이란 이름으로 받은 객체를 results로 저장하고,

Route를 통해 받아올 객체가 없다면 뭔가 에러가 있으므로 error 페이지로 이동시킨 다음, null을 리턴해 이후 렌더링을 막는다.


results 객체를 뜯어보면서 undefined인지, 감점 점수가 0점인지 체크했다.

(감점이 0점 초과면 틀린 사항이므로, 학생들에게 간단한 피드백을 적어주기 위해서다)


// file: 'ResultProcess.js'

  ...

  return (
        <>
            <AppBar position="fixed" style= >
                <Toolbar className={classesStyle.toolbar}> 
                    <Link
                        variant="h3"
                        underline="none"
                        color="inherit"
                        className={classesStyle.title}
                        href="/"
                    >
                        <img src="/assets/logo.png" alt="logo" className={classesStyle.logo} />
                    </Link>
                </Toolbar>
            </AppBar>
            <SectionLayout backgroundClassName={classesStyle.background} classes={classesLayout}>
                {}
                <img style= src={backgroundImage} alt="prioirty" />
                <Typographic color="inherit" align="center" variant="h2" marked="center" className={classesStyle.h2}>
                    { results.studentNum } _ 채점 완료!
                </Typographic>

                {existsInPolicy.feedback &&
                    <Typographic color="inherit" align="center" variant="h3" >
                        {existsInPolicy.feedback}
                        { results.result } / { results.point }
                    </Typographic>
                }
                
                {existsInPolicy.feedback && existsInPolicy.count === false &&
                    <Typographic color="inherit" align="left" variant="subtitle1" className={classesStyle.h6}>
                        <PlaylistAddCheckRoundedIcon color="error" /> &nbsp;
                        (-{results.count!.deductedPoint}) 지정된 필드, 메서드 개수 또는 (필요하다면) enhanced for문이 부족한  같습니다.
                    </Typographic>
                }
                
                ...
                


existsInPolicy 객체에서 feedback 여부와 각각 채점 요소를 조합해 Typography 컴포넌트를 조건부 렌더링한다.

만약 feedback이 비활성화되어 있거나, 틀린 게 없어 감점이 0점이라면 피드백이 출력되지 않는다

피드백 상단에는 학생이 얻은 점수 / 총점 형식으로 출력해, 자신의 점수를 바로 알 수 있게 했다. (피드백 비활성화 시 점수도 알 수 없다!)


🔥 Typescript로 다시 작성!

찜찜해서 TS로 어떻게 바꿀지 계속 고민했는데, 첫 번째 시도에서 바로 풀렸다!

먼저 Route로 넘겨오는 채점 결과를 detail로 한 번 더 감싸지 않고, 그냥 받는다.


// file: 'SectionClass.tsx'

    ...
    
    const handleCreate = (status: boolean, grading: Object) => {
        if (!status)
            props.history.push('/error');

        else
            props.history.push({
                pathname: `${props.match.url}/success`,
                state: grading, // detail을 없애고 넘겨준다!
            });
    }
    
    ...


다음으로, 받아온 객체를 위해 인터페이스를 선언한다.


// file: 'index.d.ts'

...

export interface GradingResultProps {
    isDirect: string,
    studentNum: string,
    result: number,
    point: number,
    count: {
        deductedPoint: number,
    } | undefined,
    compile: {
        deductedPoint: number,
    } | undefined,
    runtimeCompare: {
        deductedPoint: number,
    } | undefined,
    classes: {
        deductedPoint: number,
    } | undefined,
    packages: {
        deductedPoint: number,
    } | undefined,
    customException: {
        deductedPoint: number,
    } | undefined,
    customStructure: {
        deductedPoint: number,
    } | undefined,
    
    ...
}
    


채점 기준이 없을수도 있으므로, undefined일 수도 있다는 것을 명시했다.

이 인터페이스를 Route로 받아온 객체의 타입으로 선언한다.


// file: 'ResultProcess.tsx'

...

function ResultProcess (props : RouteComponentProps<RouteParamsProps>) {
    const classesStyle = useStyles();
    const classesLayout = useStylesLayout();

    if (props.location.state === undefined) {
        props.history.push('/error');
        return null;
    } 
    
    else {
    
    const results = props.location.state as GradingResultProps;   // detail을 빼고, 인터페이스를 타입으로 준다!
    
    ...
    


그래도 ESLint가 객체가 undefined인 것 같습니다.라고 경고하는데, results 변수를 사용하는 곳에 ! (Nullable)을 붙인다.


// file: 'ResultProcess.tsx'

...

return (
        <>
           ...
                
                {existsInPolicy.feedback && existsInPolicy.count === false &&
                    <Typographic color="inherit" align="left" variant="subtitle1" className={classesStyle.h6}>
                        <PlaylistAddCheckRoundedIcon color="error" /> &nbsp;
                        (-{results.count!.deductedPoint}) 지정된 필드, 메서드 개수 또는 (필요하다면) enhanced for문이 부족한 것 같습니다.
                    </Typographic>
                }


                {existsInPolicy.feedback && existsInPolicy.classes === false &&
                    <Typographic color="inherit" align="left" variant="subtitle1" className={classesStyle.h6}>
                        <PlaylistAddCheckRoundedIcon color="error" /> &nbsp;
                        (-{results.classes!.deductedPoint}) 클래스 : 지정된 클래스가 없습니다.
                    </Typographic>
                }
                
                ...
    

results의 객체를 참조하는 곳에 Nullable을 붙여주면, 객체의 Nullundefined를 허용하기 때문에 오류가 사라진다.

객체가 Null이거나 undefined일 때 렌더링하지 않는 방식으로 처리를 해줬기 때문에 허용해도 괜찮을 것 같다.


Grading Result


문제 없이 잘 동작한다👍



🔥 TroubleShooting & Review

Issue:

  • JS로 돌린 또 하나의 컴포넌트가 될 뻔 했으나…!
  • 만약 detail을 빼지 않았다면, 어떻게 처리할 수 있을까? props.location.state.detail은 먹히지 않는다.

Review

  • 피드백 제공 방식을 세분화했으면 좋겠다는 피드백이 있었다. 현재 피드백 제공 버튼을 누르지 않으면 채점 완료~만 보여주는데, 이를 채점 완료만, 채점 완료 + 점수, 채점 완료 + 점수 + 피드백 단계로 일단 나눠보는 게 어떻냐는 것. 백엔드에 넘겨주는 level만 추가하면 될 것 같다.