dart-code-metrics icon indicating copy to clipboard operation
dart-code-metrics copied to clipboard

[New rule] Avoid forming and after that returning lists in loops

Open PlugFox opened this issue 4 years ago • 4 comments

Avoid forming and after that returning lists in loops. Because double memory consumption with more reduant code.

With iterable result you can transform it to Stream For example, to avoid affecting the interface and event loops: https://dartpad.dev/6fad9242381bf89af4c1f6110c429e1c?null_safety=true

With iterable result you can much more effective with .take(n).toList() or .first or .isEmpty or .map<T>(...).toList() afterwards.


This is a very critical moment for collections displayed in lists, code generation, static analysis, parsing, reading files, converting collections.

Codegeneration, parsing, transforming must operate with Iterable not List, it's faster, lazy, less RAM consumption.


[X] Suggests an alternate way of doing something (suggestion)

Example:

BAD:

List<int> bad() {
  final list = <int>[];
  for (var i = 0; i < 1000; i++) {
    list.add(i);
  }
  return list;
}

GOOD:

Iterable<int> good() sync* {
  for (var i = 0; i < 1000; i++) {
    yield i;
  }
}

GOOD:

Iterable<int> good() => Iterable<int>.generate(
      1000,
      (i) => i,
    );

PlugFox avatar Sep 01 '21 08:09 PlugFox

As an example of incorrect code from third party developers image

As an example of correct code from SDK image

PlugFox avatar Sep 01 '21 08:09 PlugFox

You can use Iterable something like this to avoid impact on Event loop and User Interface

!!! THIS IS NOT TESTED AND PRODUCTION READY CODE !!!

Stream<T> fixEventLoop<T extends Object?>(Iterable<T> iterable) {
  final stopwatch = Stopwatch();
  StreamSubscription<T>? sub;
  final sc = StreamController<T>(
    onListen: stopwatch.start,
    onPause: () => sub?.pause(),
    onResume: () => sub?.resume(),
    onCancel: () {
      sub?.cancel();
      stopwatch.stop();
    },
  );
  sub = Stream<T>.fromIterable(iterable).asyncMap((value) async {
    if (stopwatch.elapsedMilliseconds > 8) {
      await Future<void>.delayed(Duration.zero);
      stopwatch.reset();
    }
    return value;
  }).listen(
    sc.add,
    onDone: sc.close,
    onError: sc.addError,
    cancelOnError: true,
  );
  return sc.stream;
}

But with List it's doesn't make sense. Because in the process of forming the list, you ALREADY hung up the interface, since you bypassed the loop completely.

PlugFox avatar Sep 01 '21 09:09 PlugFox

That's an interesting problem, but I think that the size of a list is a key issue here, if we'll implement such rule - because for small lists, like from 5 to 100 items, it doesn't look like a needed optimization and looks more like a false-positive to me. What do think? Maybe you have some measurements when the size of the list really starts matter?

incendial avatar Sep 04 '21 14:09 incendial

@incendial if you need download and save some picture. Of couse you can download binary data in Uint8List in RAM and after that save it as File.

But it would still be correct to connect the download stream with IOSink.

So it is here. I thought you were doing these rules in order to write the correct code, and not protect the wrong one)

In addition, the "correct" option is shorter, beautiful, and more practical)

PlugFox avatar Sep 04 '21 19:09 PlugFox