【问题标题】:Error handling: how to properly prioritize exceptions错误处理:如何正确确定异常的优先级
【发布时间】:2020-04-09 16:56:14
【问题描述】:

有两个功能:一个下载excel文件(ExcelFileUploadView(APIView)),另一个处理下载的文件(def parse_excel_rfi_sheet)。 函数parse_excel_rfi_sheetExcelFileUploadView(APIView)内部被调用

class ExcelFileUploadView(APIView):
    parser_classes = (MultiPartParser, FormParser)
    permission_classes = (permissions.AllowAny,)

    def put(self, request, format=None):
        if 'file' not in request.data:
            raise ParseError("Empty content")
        f = request.data['file']
        filename = f.name
        if filename.endswith('.xlsx'):
            try:
                file = default_storage.save(filename, f)
                r = parse_excel_rfi_sheet(file)
                status = 200
            except:
                raise Exception({"general_errors": ["Error during file upload"]})
            finally:
                default_storage.delete(file)
        else:
            status = 406
            r = {"general_errors": ["Please upload only xlsx files"]}
        return Response(r, status=status) 

def parse_excel_rfi_sheet(file):
    workbook = load_workbook(filename=file)
    sheet = workbook["RFI"]
    curent_module_coordinate = []
    try:
        ....
        curent_module_coordinate.append(sheet['E688'].value)  
        curent_module_coordinate.append(sheet['E950'].value)  
        if check_exel_rfi_template_structure(structure=curent_module_coordinate):
            file_status = True
        else:
            file_status = False
    except:
        raise Exception({"general_errors": ["Error during excel file parsing. Unknown module cell"]})

问题是当parse_excel_rfi_sheet内部发生错误时,我看不到{"general_errors": ["Error during excel file parsing. Unknown module cell"]}的调用 相反,我总是看到电话

{"general_errors": ["Error during file upload"]}

这就是为什么我无法理解错误发生在哪个阶段:在下载文件的那一刻或在处理的那一刻。 怎么改?

【问题讨论】:

    标签: python django-rest-framework


    【解决方案1】:

    因为您从ExcelFileUploadView 调用parse_excel_rfi_sheet 时,每当从parse_excel_rfi_sheet 引发异常{"general_errors": ["Error during excel file parsing. Unknown module cell"]} 时,来自ExcelFileUploadView 的函数try 块失败并到达except 并引发异常{"general_errors": ["Error during file upload"]}

    您可以通过打印ExcelFileUploadView 函数引发的异常来验证这一点。 将 try 块更改为以下内容:

    try:
        file = default_storage.save(filename, f)
        r = parse_excel_rfi_sheet(file)
        status = 200
    except Exception as e:
        print("Exception raised ", e)
        raise Exception({"general_errors": ["Error during file upload"]})
    

    【讨论】:

      【解决方案2】:

      您的问题来自绝对捕获所有异常,首先在parse_excel_rfi_sheet 中,然后再次在您的put 方法中。裸的 except 子句 (except: whatever_code_here) 和大的 try 块都是反模式 - 您只想在给定点捕获您期望的确切异常(使用 except (SomeExceptionType, AnotherExceptionType, ...) as e:,并在您的 @ 中包含尽可能少的代码987654326@ 块,所以你确信你知道异常来自哪里。

      此规则的唯一例外(没有双关语)是更高级别的“捕获所有”处理程序的情况,用于捕获意外错误,记录它们(所以你有一个跟踪所发生的事情),并向用户显示一条友好的错误消息 - 但即便如此,您也不想要一个空的 except 子句,而是一个 except Exception as e

      TL;DR:永远不要假设引发了哪个异常、在哪里以及为什么引发了任何事情,也永远不要默默地传递异常(至少记录它们 - 并检查你的日志)。

      【讨论】:

      • 谢谢您,我根据您的补充做了一个审核代码,现在我收到一个特定错误而不是一般消息。
      【解决方案3】:

      raise Exception(...) 生成一个新的Exception 实例并引发该实例。 这意味着,put 中的 try ... except 有效地丢弃了它捕获的异常并将其替换为带有消息“文件上传期间出错”的新异常,这就是您总是看到相同消息的原因。

      处理此问题的一种简洁方法是定义Exception 的自定义子类(例如InvalidFormatException)并在parse_excel_rfi_sheet 中提升该子类,在except 中有两个不同的except 案例put

      class InvalidFormatException(Exception):
          pass
      
      [...]
      
      def parse_excel_rfi_sheet(file):
          workbook = load_workbook(filename=file)
          sheet = workbook["RFI"]
          curent_module_coordinate = []
          try:
              ....
              curent_module_coordinate.append(sheet['E688'].value)  
              curent_module_coordinate.append(sheet['E950'].value)  
              if check_exel_rfi_template_structure(structure=curent_module_coordinate):
                  file_status = True
              else:
                  file_status = False
          except:
              raise InvalidFormatException({"general_errors": ["Error during excel file parsing. Unknown module cell"]})
      

      您的put 则变为:

          def put(self, request, format=None):
              if 'file' not in request.data:
                  raise ParseError("Empty content")
              f = request.data['file']
              filename = f.name
              if filename.endswith('.xlsx'):
                  try:
                      file = default_storage.save(filename, f)
                      r = parse_excel_rfi_sheet(file)
                      status = 200
                  except InvalidFormatException:
                      raise # pass on the exception
                  except:
                      raise Exception({"general_errors": ["Error during file upload"]})
                  finally:
                      default_storage.delete(file)
              else:
                  status = 406
                  r = {"general_errors": ["Please upload only xlsx files"]}
              return Response(r, status=status)
      

      警告:正如 cmets 对此答案所指出的那样,请注意 - 尽管没有直接询问 - 应进一步修改 OP 的代码以删除裸露的 except: 子句,如 this is probably not the expected behaviour .

      【讨论】:

      • 一个关于异常处理的答案,使用裸 except 子句是一个非常错误的答案。正确的异常处理规则 #1:从不使用裸的 except 子句。
      • @brunodesthuilliers 问题不在于正确的异常处理,我没有声称我的解决方案在“正确”异常处理方面是正确的,因为那是题外话。 OP 想知道为什么他的异常被取消了,我对此进行了解释,并提供了一个如何解决此问题的示例。深入研究 OP 代码的可能谬误是针对 codereview.stackexchange.com,而不是 SO。
      • @GPhilo 除了听起来它不像 OP 期望的那样工作,它对 CR 无效。
      • @Mast 这个问题当然不属于CR。我只是说我详细说明为什么 OP 的代码不好(当然,除了请求的问题之外)将是题外话。
      • @GPhilo 我想我们必须同意不同意。但仍然感谢您添加警告。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-06-26
      • 2021-05-22
      • 2011-01-29
      • 2016-12-30
      • 2023-03-07
      • 2010-12-21
      • 1970-01-01
      相关资源
      最近更新 更多